看了一下我司官网的webpack打包出来的大小情况,发现有很多可以优化的点,比如 lodash、moment.js、antd等等;
本文主要围绕webpack的打包优化,并根据业务情况适当的做减法。
优化前分析
优化前一定要有一个界面能记录目前的打包情况,推荐用webpack-bundle-analyzer这个包, 它可以看到打包后每个模块的大小,还能给出gizp压缩后的大小,在生产环境中加载的模块都是经过gzip压缩过的,可以作为真实访问的大小依据。
安装也很简单:
// cli
npm install --save-dev webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
注意生产环境(production)是代表线上真实的环境,所以analyzer要对生产环境的包进行分析的,所以我配置了一下本地打包生产环境的构建配置,在package.json加入下面的配置:
"scripts": {
...
"local_production": "cross-env NODE_ENV=local_production npm run build"
}
然后在webpack配置里面判断process.env.NODE_ENV === 'local_production'
,构建production环境的构建并且加入analyzer分析生产环境打包出来的情况。
这里是我的项目用analyzer生成出来的包大小情况(打包前)
主要看index.xxxx.js,它包含了所有的公共依赖,我们要做的就是减少不必要的公共资源的体积,可以减少大量不必要的代码。
逐个击破
分析antd
从上面的可以看出来antd.less占了很大部分面积,因为我要在项目中自定义theme,但是官方的那套配置的形式来自定义theme只能修改变量,不能改组件,所以我先加载所有的antd.less再在后面接着加载一个theme.less用于修改主题变量和修改antd组件样式。
- 可能是我当时搭项目的时候想太多了,由于是官网项目,所有的组件都是根据ui来自己写的,很少用到antd的组件,项目开发了几十个页面了也没有用到这种自定义组件的情况,所以其实可以不加载这个庞大的antd.less,然后antd按需加载是必须的。
- 后来发现我项目中用到的antd组件只有两个(轮播和单选框),其实轮播是可以用react-slick替代的,而单选框更是可以自己实现的,所以大胆的直接把antd给移除掉了,用其他插件替代即可。
移除了antd之后index包小了三百多k,这还远远不够,接着看下面的优化点
优化lodash
lodash也是需要优化按需加载的方式的,推荐这篇教程Webpack按需打包Lodash的几种方式, 按照教程改进后,lodash 小了500多k。
优化moment
其实moment引进来的时候会带有很多语言包的,我们只用到了其中一个中文的包,所以其他语言包都可以去掉,
plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
]
后来又发现项目中只用到了moment().format()这个方法,由于moment.js只有一个大的moment.js模块,没有按模块分开写,无法按需打包,那么其实我们可以自己实现个简易版的moment来替代moment.js,下面是我找到的实现简易版moment代码:
// 简易版moment代替moment.js
class Moment {
private date:Date;
constructor(arg = new Date().getTime()) {
this.date = new Date(arg);
}
padStart(num) {
num = String(num);
if (num.length < 2) {
return '0' + num;
} else {
return num;
}
}
unix() {
return Math.round(this.date.getTime() / 1000);
}
static unix(timestamp) {
return new Moment(timestamp * 1000);
}
format(formatStr) {
const date = this.date;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const week = date.getDay();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
const weeks = ['一', '二', '三', '四', '五', '六', '日'];
return formatStr.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|h{1,2}|m{1,2}|s{1,2}/g, (match) => {
switch (match) {
case 'YY':
return String(year).slice(-2);
case 'YYY':
case 'YYYY':
return String(year);
case 'M':
return String(month);
case 'MM':
return this.padStart(month);
case 'D':
return String(day);
case 'DD':
return this.padStart(day);
case 'd':
return String(week);
case 'dd':
return weeks[week];
case 'ddd':
return '周' + weeks[week];
case 'dddd':
return '星期' + weeks[week];
case 'h':
return String(hour);
case 'hh':
return this.padStart(hour);
case 'm':
return String(minute);
case 'mm':
return this.padStart(minute);
case 's':
return String(second);
case 'ss':
return this.padStart(second);
default:
return match;
}
});
}
}
export const moment = (arg) => {
return new Moment(arg);
};
这样就直接可以把moment.js 干掉了,包体积又小了不少。
下面是优化后的analyzer生成出来的包大小情况
包体从2.7M优化到了1.7M,gzip从297k减小到212k,访问虽然只是快了一点点,但在低网速环境下访问还是看得到区别的。
首屏加载视觉优化
接下来讲一个跟包大小无关又很重要的优化点,就是单页应用的第一个入口html,正常情况下入口html只是用来加载js包,等js加载完之后才渲染出相关界面出来,这个入口html本身没有内容展示,但它是整个网站的第一个请求,取到这个入口html之后才开始加载js,等到加载完js才开始渲染界面,这段时间是占网站整体加载时间最多的,如下图:
第一个请求只要128ms,直到加载完公共js渲染出界面需要1s左右,这时候如果入口index没内容的话那就是纯粹的白屏时间了,所以我们应该好好利用这个入口index.html,可以做一个骨架屏或者loading动画,能让用户在等白屏时间里能够有个界面能看到,停留时间会更长一些,也能让用户以为这个网站一下就刷出来看到东西的感觉。
对于这个入口index的利用,我是加入了顶部导航栏进去的,让用户可以第一眼看到导航栏知道有什么导航项,而且也是可以点进去的,而内容区对于不同的路径访问会有不同的界面,所以我就简单的弄个loading即可。
至此,这一版优化减少了加载的时间,同时合理利用了入口index作为loading页,提高用户体验。
总结
前端优化工作是一个长期且复杂的工作,有很多可以考虑的地方,可以根据网络环境、框架、用户群体、业务情况、代码结构等多个方面合理地安排选择优化方案,本文只是我对于现有公司官网的优化的一部分,在这里分享给大家,如果觉得有用就点个赞吧👍