
Giảm 13kb bundle size khi thay thế Axios bằng Fetch
Mình sử dụng axios khá lâu, hầu như tất cả các project của mình trước kia. Gần đây mình bắt đầu optimize project của mình, sử dụng webpack-bundle-analyze để check tất cả các package trong node_modules để optimize, mình phát hiện axios chiếm gần 35KB.
Chậc, vừa mất thời gian install package trong lúc build lại tăng thêm 13KB bundle size chỉ cho một lib handle XMLHttpRequest thôi á. Ngoài việc sử dụng axios và gọi những method Get/Post/Push/Update/Delete ra thì mình cũng không sử dụng thêm những tính năng nào của axios hết.
Thế là mình bắt đầu thay thế axios bằng fetch API. fetch được support native trên hầu hết những browser (trừ IE ra nha), có nghĩa là bạn không cần phải cài đặt thêm package nào vào để sử dụng được fetch, giảm thời gian build + loadtime của ứng dụng.
Tuy nhiên với những browser cổ xưa như IE thì không được support (ngay đến Microsoft còn quay lưng với IE nói gì các nhà phát triển)
Chia tay axios......
axios em rất tốt.... nhưng anh rất tiếc....
Nước mắt của axios anh lau bằng fetch
Chúng ta sử dụng duy nhất một function fetch(url,options), việc thay đổi các method,payload,header... của fetch là do chúng ta thay đổi input của tham số thứ options
fetch API:
fetch('https://codefun.dev',{
method:"GET || POST || GET || DELETE || UPDATE", // http method
headers: {
Authorization: '',
'Content-type':'application/json',
credentials: 'same-origin',
...others_header_field,
},
body:JSON.stringify(data),
...orthers_options_field
})
Vậy những biến như method, header, body là những giá trị mình cần tham số hoá, mình bắt đầu tách function fetch này ra như sau.
Tham số hoá fetch()
function objectToQueryString(params={}){
// convert object to query params if method = GET
}
function formatParams(params={}){
//validate + format params if method = POST
}
async function request(url = '', payload: RequestPayload, method = 'GET') {
const { headers = {}, params = {} } = payload || {};
const options: any = {
method,
headers: {
Authorization: '',
'Content-type': 'appliation/json',
...headers,
},
};
if (method === 'GET') {
url += '?' + objectToQueryString(params);
} else {
options.body = formatParams(params);
}
const response = await fetch(url,options);
// handle response
}
Fetch Response
Khi thực hiện một XMLHttpRequest , nếu httpStatus >= 400 thì mặc định axios sẽ reject => bạn sẽ handle error trong phần catch
Nhưng với fetch thì khác, khi thực hiện một request, fetch sẽ trả về Response object là một promise và luôn luôn resolve về kết quả khi Promise này được xử lý, bất kể httpStatus code là gì. Thay vì reject thì status code >=400 thì fetch vẫn resolve Response nhưng lúc này field ok sẽ bằng false.
Success Response:
Error response
Xử lý fetch response
Tại vì fetch() return về một Promise Respone object, nên trước tiên mình phải resolve cái promise này trước đã
async function request(url, payload, method = 'GET') {
......
const response = await fetch(url,options);
// handle response
const resp = await response.json();
if (response.status === 401) {
handleUnAuthorize() // handle error
}
if (response.status >= 200 && response.status <= 300) {
return { http_status: response.status, ...resp };
}
throw {
http_status: response.status,
error: { ...resp },
};
}
Khi migrate từ axios qua fetch, mình muốn giữ lại function handle logic trước kia nên mình return về kết quả giống như axios trả về để code của mình không phải sửa nhiều
Code gần hoàn chỉnh sẽ như sau:
function objectToQueryString(params={}){
// convert object to query params if method = GET
}
function formatParams(params={}){
//validate + format params if method = POST
}
async function request(url = '', payload: RequestPayload, method = 'GET') {
const { headers = {}, params = {} } = payload || {};
const options: any = {
method,
headers: {
Authorization: '',
'Content-type': 'appliation/json',
...headers,
},
};
if (method === 'GET') {
url += '?' + objectToQueryString(params);
} else {
options.body = formatParams(params);
}
const response = await fetch(url,options);
// handle response
const resp = await response.json();
if (response.status === 401) {
handleUnAuthorize() // handle error
}
if (response.status >= 200 && response.status <= 300) {
return { http_status: response.status, ...resp };
}
throw {
http_status: response.status,
error: { ...resp },
};
}
Implement function request
Tới đây bạn có thể dùng hàm request được rồi, nhưng mình muốn tiện hơn trong quá trình code cũng như truyền một số biến đặc biệt cho mỗi method sau này nên mình sẽ viết thành các hàm Get/Post/Put/Delete implement từ hàm request như sau:
export const Get = (url, payload?) => {
return request(url, payload);
};
export const Post = (url, payload?) => {
return request(url, payload, 'POST');
};
export const Put = (url, payload?) => {
return request(url, payload, 'PUT');
};
export const Delete = (url, payload?) => {
return request(url, payload, 'DELETE');
};
Implement methods
Get:
export const getNewestPosts = ({page = 0}) => async dispatch => {
try {
const data = await Get(`/posts`, {
params: {
filter: FILTER_KEY.NEWEST,
},
});
return dispatch({
type: homeEnum.getNewPostSuccess,
payload: data.data,
});
} catch (e) {
return dispatch({
type: homeEnum.getNewPostError,
payload: e,
});
}
};
Post:
export const newPost=(params)=>async dispatch=> {
try {
await Post('/post', {params})
return dispatch({
type: postEnum.addNewPostSuccess,
payload: data.data,
});
} catch (e) {
return dispatch({
type: postEnum.addNewPostError,
payload: e,
});
}
}
Put:
export const newPost=(params)=>async dispatch=> {
try {
await Put('/post', {params})
return dispatch({
type: postEnum.updateNewPostSuccess,
payload: data.data,
});
} catch (e) {
return dispatch({
type: postEnum.updateNewPostError,
payload: e,
});
}
}
Kết bài
Mình và các bạn trong team đang có kế hoạch organic hoá project, giúp giảm bundle size và cải thiện thời gian build + tải trang.
Những thư viện sử dụng ở ngoài viết ra để cover cho hầu hết các trường hợp, cho cả một cộng đồng xài, việc bạn chỉ xài 1,2 function mà bundle cả một lib nặng mấy chục kb, thậm chí lên tới MB như lodash, moment thì quả là lãng thí.