一、代码体积优化
- JavaScript 压缩:
在mode=production
下,Webpack
会自动压缩代码,我们可以自定义自己的压缩工具,这里推荐terser-webpack-plugin
,它是Webpack
官方维护的插件,使用terser
来压缩JavaScript
代码。UglifyJS
在压缩ES5
方面做的很优秀,但是随着ES6
语法的普及,UglifyJS
在ES6
代码压缩上做的不够好,所以有了uglify-es
项目,但是之后uglify-es
项目不在维护了,terser
是从uglify-es
项目拉的一个分支,来继续维护。terser-webpack-plugin
具有跟Uglifyjs-webpack-plugin
相同的参数。 - 总结:
- 合理划分代码职责,适当使用按需加载方案;
- 善用
webpack-bundle-analyzer
插件,帮助分析Webpack
打包后的模块依赖关系; - 设置合理的
SplitChunks
分组; - 对于一些
UI
组件库,例如AntDesign
、ElementUI
等,可以使用bable-plugin-import
这类工具进行优化; - 使用
lodash、momentjs
这类库,不要一股脑引入,要按需引入,momentjs
可以用date-fns
库来代替; - 合理使用
hash
占位符,防止hash
重复出现,导致文件名变化从而HTTP
缓存过期; - 合理使用
polyfill
,防止多余的代码; - 使用
ES6
语法,尽量不使用具有副作用的代码,以加强Tree-Shaking
的效果; - 使用
Webpack
的Scope Hoisting
(作用域提升)功能,而Scope Hoisting
是webpack
通过ES6
语法的静态分析,分析出模块之间的依赖关系,尽可能地把模块放到同一个函数中
- CSS 压缩:
cssnano
是基于postcss
的一款功能强大的插件包,它集成了30
多个插件,只需要执行一个命令,就可以对我们的CSS
做多方面不同类型的优化,比如:
- 删除空格和最后一个分号;
- 删除注释;
- 优化字体权重;
- 丢弃重复的样式规则;
- 压缩选择器;
- 减少手写属性;
- 合并规则;
- 图片资源优化:
通常我们的代码体积会比图片体积小很多,有的时候整个页面的代码都不如一张头图大。好在图片资源不会阻塞浏览器渲染,但是不合理的图片大小也会消耗一定的代码。可以使用:url-loader、svg-url-loader 和 image-webpack-loader
来优化图片,还可以使用雪碧图来优化图片资源,如下所示:
url-loader
可以按照配置将小于一定体积的静态文件内联进我们的应用。当我们指定了limit
这个options
选项,它会将文件编码成比无配置更小的Base64
的数据url
并将该url
返回,这样可以将图片内联进JavaScript
代码中,并节省一次HTTP
请求。svg-url-loader
的工作原理类似于url-loader
,除了它利用URL encoding
而不是Base64
对文件编码,对于SVG
图片来说,svg-url-loader
的这种方式这是有效的,因为SVG
文件本质上是纯文本文件,这种URL encoding
编码规模效应更加明显。- 如果我们的项目中小图片特别多,例如有很多
icon
类的图标,这时候则推荐使用雪碧图(CSS Sprite)
来合并这些小图到一张大图中,然后使用background-position
来设置图片的位置,通过这样的方式可以节省多次小图片的请求。 - 对于大图片来说,可以使用
image-webpack-loader
来压缩图片,image-webpack-loader
它支持JPG、PNG、GIF 和 SVG
格式的图片,因此我们在碰到所有这些类型的图片都会使用它。
二、增强缓存命中率
- 浏览器缓存策略和
Webpack
缓存相关配置,使用浏览器的持久化缓存方案分两步走,第一步是我们一般会将静态资源(JavaScript 、CSS、图片字体文件等)
这些不经常变动的文件寻找更合适的服务器存放,比如放到CDN
服务器上,并且配置单独的域名,比如百度常用的CDN
域名是:x.bdimg.com
和x.bdstatic.com
域名,这样做的好处是:
- 保证动静分离,将动态页面和静态资源分开部署有利于服务的更好维护;
CDN
可以更加接近用户的终端,提供加速服务,详细可以查看CDN
的原理;- 静态资源不会具有动态逻辑,单独的域名可以减少页面请求中的
Cookie
等不必要字段,减少HTTP
带宽。 - 将静态资源存放到单独的服务器之后,需要做的是配置合理的
HTTP
缓存相关协议,比如我们使用Cache-Control
告诉浏览器,当前文件的max-age: Cache-Control: max-age=31536000
上面设置了文件的缓存时间是一年(31536000=360024365)
。
-
完成第一步之后,第二步就是要针对发生了变更的静态资源进行重命名,这样静态的文件虽然使用了
CDN
和强缓存,但是只要内容变化,那么文件的路径(网址)发生了变化,浏览器还是会重新请求下载的: -
所以缓存相关的
Webpack
打包优化方案,合理利用HTTP
请求头的缓存相关字段,然后配合Webpack
的chunkhash
和contenthash
可以做到根据文件内容和依赖关系变化而增强浏览器缓存,另外根据代码的变更频率合理的拆分代码也能够起到缓存的最大作用,Webpack
中拆分代码用到的是动态加载方式和optimization.splitChunks
。
三、功能拆分代码
- Webpack 代码拆分方式:
在Webpack
中,总共提供了三种方式来实现代码拆分(Code Splitting)
:
entry
配置:通过多个entry
文件来实现;- 动态加载(按需加载):通过写代码时主动使用
import()
或者require.ensure
来动态加载; - 抽取公共代码:使用
splitChunks
配置来抽取公共代码。
- 设置更高权重的
cacheGroup
;使用test
函数针对类型为js
和css
分别设置各自的cacheGroup
。另外我们还可以使用test
函数实现更细化的匹配,例如:忽略一部分文件等。
四、速度优化
-
当
Webpack
的项目文件多了之后,构建过程会越来越慢,这时候就需要做一些构建速度方面的优化手段了。影响Webpack
构建速度的有两个「大户」:一个是loader
和plugin
方面的构建过程,一个就是压缩,把这两个东西优化起来,可以减少很多发布的时间。 -
压缩速度优化:
相对于构建过程而言,压缩相对我们来说只有生产环境打包才会做,而且压缩我们除了添加cache
和多线程支持之外,可以优化的空间较小。我们在使用terser-webpack-plugin
的时候可以通过下面的配置开启多线程和缓存:
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: true, // 开启缓存
parallel: true // 多线程
})
]
}
};
- 从构建速度角度来如何提升打包速度,分别从构建过程和压缩两个维度来介绍提升速度的方法,其中压缩角度可以优化的点比较少,而在构建过程中可以从减少查找过程、多线程、提前编译和缓存多个角度来优化,其中重点介绍了使用减少查找过程的几种配置方式,使用
thread-loader
和HappyPack
来开启多线程,使用DLLPlugin
来预先编译和使用babel-loader
的缓存等方法。实际项目中并不是非要做构建速度的优化,如果项目简单完全没有必要,要具体问题具体分析。不管怎样,保持Webpack
版本最新是一个既简单又效果不错的方式!
五、Tree-Shaking
-
Tree-Shaking
是一个前端术语,本意为摇树的意思,在前端术语中通常用于描述移除JavaScript
上下文中没用的代码,这样可以有效地缩减打包体积。关于Tree-Shaking
,Webpack
官方文档有一段很形象的描述:你可以将应用程序想象成一棵树。绿色表示实际用到的源码和library
,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。 -
Tree-Shaking 实现原理:
Tree-Shaking
的本质是消除无用的JavaScript
代码。无用代码消除(Dead Code Elimination)
广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(Dead Code Elimination)
。Tree-Shaking
是DCE
的一种新的实现,Javascript
同传统的编程语言不同的是,JavaScript
绝大多数情况是在浏览器中执行,需要通过网络进行加载,然后解析JavaScript
文件再执行。 -
Webpack Tree-Shaking 代码实战:
在Webpack
中,Tree-Shaking
是需要配合mode=production
来使用的,这是因为Webpack
的Tree-Shaking
实际分了两步来实现:
Webpack
自己来分析ES6 Modules
的引入和使用情况,去除不使用的import
引入;- 借助工具(如
uglifyjs-webpack-plugin
和terser-webpack-plugin
)进行删除,这些工具只在mode=production
中会被使用。
-
配置sideEffects:
Webpack
的项目中,可以在package.json
中使用sideEffects
来告诉webpack
哪些文件中的代码具有副作用,从而对没有副作用的文件代码可以放心的使用Tree-Shaking
进行优化。如果自己的项目是个类库或者工具库,需要发布给其他项目使用,并且项目是使用ES6 Modules
编写的,没有副作用,那么可以在该项目package.json
设置sideEffects:false
来告诉使用该项目的webpack
可以放心的对该项目进行Tree-Shaking
,而不必考虑副作用。 -
Tree-Shaking
对前端项目来说可谓意义重大,是一个极致优化的理想世界,是前端进化的又一个终极理想。但是理想是美好的,现实是骨感的,真正发挥Tree-Shaking
的强大作用,还需要我们在日常的代码中保持良好的开发习惯:
- 要使用
Tree-Shaking
必然要保证引用的模块都是ES6
规范的,很多工具库或者类库都提供了ES6
语法的库,例如lodash
的ES6
版本是lodash-es
; - 按需引入模块,避免「一把梭」,例如我们要使用
lodash
的isNumber
,可以使用import isNumber from 'lodash-es/isNumber';
,而不是import {isNumber} from 'lodash-es';
- 减少代码中的副作用代码。另外一些组件库,例如
AntDesign
和ElementUI
这些组件库,本身自己开发了Babel
的插件,通过插件的方式来按需引入模块,避免一股脑的引入全部组件。