前言
最近优化了一些webpack项目的构建体积与速度,结合公司做的一些默认配置,大部分应用打包速度已经快到飞起,在这记录一下自己和公司做的一些webpack优化。
过程
帮助我们分析的插件
下载 webpack-bundle-analyzer 和 speed-measure-webpack-plugin
npm i webpack-bundle-analyzer speed-measure-webpack-plugin
webpack-bundle-analyzer
webpack-bundle-analyzer 可以查看构建前引入包的体积
stat size: webpack从入口文件打包递归到的所有模块体积
parsed size: 解析与代码压缩优化后输出到dist目录的体积(dist文件夹内代码压缩<UglifyJS>后的js文件)
gzipped size: 开启Gzip之后的体积(文件由a.js变为a.js.gz)
针对 gzipped,我们知道webpack开启gzip,那如何开启呢?
下载 compression-webpack-plugin,并进行配置
额外知识:
线上nginx还需要开启gzip,对代码进行压缩(响应头中会有 content-encoding: gzip)
http { //在 http中配置如下代码,
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 8; #压缩级别
gzip_buffers 16 8k;
#gzip_http_version 1.1;
gzip_min_length 100; #不压缩临界值
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
}
为什么webpack对代码进行gzip压缩了,nginx还要再来一次gzip压缩?
将nginx配置开启gzip压缩,nginx会根据配置情况对指定的类型文件,进行压缩。主要针对js与css.如果文件路径中存在与原文件同名(加了个.gz),nginx会获取gz文件,如果找不到,会主动进行gzip压缩。
我们本地服务devServer有一个 compress: true 配置(响应头会携带content-encoding: gzip),这个的作用相当于nginx开启gzip,对本地代码热更新文件进行压缩,热更新及起服务速度更快,这样可以代替线上使用压缩插件,nginx配置。
speed-measure-webpack-plugin
上中下三部分 分别代表 文件输出时间、plugin工作时间、loader工作时间
如果显示红色的话,说明耗时时间过长,我们可以找到对应的plugin\loader去查看为什么耗时这么久,进而进行优化。比如sass-loader比较慢,我们看看换成less-loader可不可以。ExtractTextPlugin比较慢,我们可不可以把这个插件copy下来,结合自身业务改写下,让他更快一点。
优化方案
一、对第三方依赖包优化(速度、体积)
部分包替换
首先我们可以针对我们使用的第三方依赖包进行优化,例如非常占内存的moment,我们可以替换为dayjs,但是如果有很多地方使用moment,我们替换的话成本比较大(每个地方),风险也比较高(有地方漏掉替换),我们可以使用webpack中的配置别名来,成本和风险都较低。
// clientChainWebpack中的配置,老webpack配置自行百度
config.resolve.alias.set('moment', 'dayjs')
这里的意思是识别 代码中moment字段,编译后全部替换为 dayjs 字段
部分包按需加载
我们经常使用loadsh这个包,但是只是使用了它的几个函数。这时候,我们有可能将整个loadsh包都打包了进去,这个时候我们可以用一些webpack的plugin来让他按需加载,例如:
npm i babel-plugin-lodash lodash-webpack-plugin
第一个为babel的plugin、第二个是webpack的plugin
我们也经常使用elementUI这种UI库,这种UI库的组件是非常占打包体积的。elementUI官网也为我们提供了babel插件 babel-plugin-component,让我们引入需要的组件。官方也为我们提供了一些方法,让我们按需加载在使用的组件。
很多第三方包都提供了按需加载的webpack插件或babel插件。
额外知识:
plugin和loader和babel有什么区别?
首先plugin和loader都是对webpack功能的拓展。
loader
loader主要是将某种格式的文件(包括css\js\ts\less\jsx\jpg等等)转化成webpack支持打包的模块。
比如style-loader、css-loader、less-loader、sass-loader、postcss-loader、url-loader(指定文件大小,小于这个指定的大小 转化为base64打进js包,否则文件单独打包)、file-loader、ts-loader、babel-loader(将es6以上浏览器不能识别的js语法转化为浏览器可以识别的js语法)。
可以看出loader的作用是单一的,只针对某项资源和某段流程(比如babel-loader只针对js资源、只针对ES6以上资源 转化 为浏览器可以识别的ES3\4\5或当前浏览器支持的js语法。)。
babel
babel这时的作用也很清晰了,就是 将es6\7\8的语法转化为浏览器可以识别的es3\es4\es5,或者将当前浏览器不能识别的js,转化为可以识别的js。
但是babel和webpack没有任何关系,babel-loader只是一个工具 用来使webpack可以使用babel 进而将es6\7\8的语法转化为浏览器可以识别的es3\es4\es5的或当前浏览器支持的js语法。
关于babel的原理,什么AST、词法分析、语法分析 这些就不再提了。
babel插件的配置,举一个简单的例子, babel-plugin-lodash,可以在.babelrc或webpack中配置
.babelrc配置
{
'presets': [],
'plugins': [
'lodash' //配置(这里并不是插件全称)
]
}
webpack配置
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: /node_modules/,
include: [resolve('src'), resolve('test')]
options: {plugins: ['lodash']}//配置(这里并不是插件全称)
}
plugin
从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。
它的配置我应该不用多说了。引入插件,实例化一下,放到webpack plugins里面。
二、图片压缩(体积)
上面我们对一些第三方包进行优化,手段是使用一些plugin、loader、甚至babel-plugin。这里我们也需要使用这些手段,进行优化,比如图片压缩,使用的image-webpack-loader
npm install image-webpack-loader --save-dev
下载完,要在webpack配置文件中配置一下,注意,这里不是babel-loader,不要去babel配置文件中配置了。
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
还有file-loader,如果图片体积小于设置的标准,转化为base64打到js包内。大于设置的标准,单独打包。
三、优化loader (速度)
优化loader搜索范围
减少loader的检索时间,比如只针对js进行检索,只在src文件夹下进行检索,不去查找node_modules下的代码等等。
module.exports = {
module: {
rules: [
test: /\.js$/, // 对js文件使用babel
loader: 'babel-loader',
include: [resolve('src')],// 只在src文件夹下查找
// 不去查找的文件夹路径,node_modules下的代码是编译过得,没必要再去处理一遍
exclude: /node_modules/
]
}
}
cache-loader缓存loader处理结果
npm i cache-loader
module.exports = {
module: {
rules: [
{
// js 文件才使用 babel
test: /\.js$/,
use: [
'cache-loader',
...loaders
],
}
]
}
}
那这么说的话,我给每个loader前面都加上cache-loader,然而凡事物极必反,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用 cache-loader。
四、合理配置(速度)
合理的配置mode参数与devtool参数
mode可设置development production两个参数
如果没有设置,webpack4 会将 mode 的默认值设置为 production
production模式下会进行tree shaking(去除无用代码)和uglifyjs(代码压缩混淆)
缩小文件的搜索范围(配置include exclude alias noParse extensions)
resolve:{
alias:{
'react':'react',
'@':path.resolve(__dirname,'../src'),
'components': path.resolve(__dirname,'../src/components'),
'assets': path.resolve(__dirname,'../src/assets'),
},
extensions:['*','.js','.json','.jsx']
},
extensions:会根据extensions定义的后缀查找文件(频率较高的文件类型优先写在前面,像lru缓存那种)
五、开启多线程(速度)
loader并行运行
受限于Node是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。那么我们可以使用一些方法将 Loader 的同步执行转换为并行,这样就能充分利用系统资源来提高打包速度了。
HappyPack和thread-loader这两个包,这块在探索,怀疑公司脚手架有类似的东西,这块要看看。
建议使用thread-loader 建议打包时间很久的可以使用。
/*
thread-loader会对其后面的loader(这里是babel-loader)开启多进程打包。
进程启动大概为600ms,进程通信也有开销。(启动的开销比较昂贵,不要滥用)
只有工作消耗时间比较长,才需要多进程打包
thread-loader必须最后执行,再次说明loader是从下往上,从右往左的执行顺序,所以想要使用thread-loader优化某项的打包速度,必须放在其后执行
*/
六、DLL(速度)
作用:
我们使用的一些第三方包依赖(node_modules中的,比如loadsh),因为代码不会变化,打包的时候不在重复打包。(减少打包时间)
原理是:
打包的时候,将第三方不需要重复打包的依赖,做成一个映射,放到manifest.json(dist文件夹下的)这个文件中。然后在webpack配置中的plugins中,引入这个映射
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./manifest.json'),
}),
],
这样下次打包就不会重新打包第三方依赖了
七、hard-source-webpack-plugin(速度)
这个我看是代替dll的技术方案
config
.plugin('hard-source-webpack-plugin')
.use(require('hard-source-webpack-plugin'))
webpack的plugin插件,缓存方案
八、externals(体积、速度)
externals详解
就是不把对应的第三方包打到js文件里去,然后线上使用cdn
九、treeshking(速度、体积)
生产环境默认开启,不多说
十、devtool设置none
生产环境默认none,不多说,没有sourceMap打包速度更快,体积更小