前言
webpack 是一个优秀的打包工具,其本身为我们做了大量优化,同时也为我们提供了大量的配置项让我们可以自定义,从而有优化空间。
在讲 webpack 优化篇之前,由于楼主主要以 vue 脚手架开始的,而且是已经升级为 webpack4 之后的优化,如果对 vue脚手架配置不太了解的同学。可以看我上一篇文章 如何优雅的升级到webpack4,或者直接看 webpack3 vue脚手架注解
下面我先讲讲vue脚手架为我们做的一些优化,不喜欢看的请跳过,然后会讲如何在优化的基础上升华一下,内容从浅到深,但是所有的方法都经过楼主考证,内容较长,请自带板凳瓜子。
vue-cli 脚手架自带优化
babel
Babel 是一个 JavaScript 编译器,能将 ES6 代码转为 ES5 代码,让你使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活的扩展。这里我不讲babel ,而是讲官方用的插件 transform-runtime,对应的插件全名叫做 babel-plugin-transform-runtime,其作用是减少冗余代码,到底是怎么减少的呢?
例如在转换 class extent 语法时会在转换后的 ES5 代码里注入 _extent 辅助函数用于实现继承:
function _extent(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
}
这会导致每个使用了 class extent 语法的文件都被注入重复的_extent 辅助函数代码,babel-plugin-transform-runtime 的作用在于不把辅助函数内容注入到文件里,而是注入一条导入语句:
var _extent = require('babel-runtime/helpers/_extent');
这样能减小 Babel 编译出来的代码的文件大小。
注意:babel-plugin-transform-runtime 必须和 babel-runtime 需要配套使用
说来惭愧,楼主试了一下,把这个插件去掉,生成文件的hash和大小并没有变化(汗,别砸,翻资料webpack 标准入门、前端工程化-webpack篇之babel-polyfill与babel-runtime(三)上有写,而且脚手架上有)。后来发现,楼主的代码中并没有es6。后来换了一个大项目,做了对比
可以发现,图右边是去掉插件的。体积明显大了一点。使用此插件可以减少重复代码,缩小项目体积。
缩小文件搜索范围
loader
使用 Loader 时可以通过 test 、 include 、 exclude 三个配置项来命中,对于我们的项目大部分都是 js,下面看看官方脚手架 js 的 babel-loader:
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
]
}
}
由于通过 npm 安装的第三方的库,都是经过 webpack 打包 es5 化了,所以这里就可以只对 include 包括的文件使用 babel-loader 解析
注意了。由于 css、less 的引入是要插入到js中的,所以并不适用于这个(把 node_modules 排除在外)方法。说到这里,多说一句,也是曾经很困扰我的 css 的 loader 解析顺序,use 的 loader 解析顺序跟数组的位置是反着的,以 less 为例,具体来讲
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.less$/,
// less 文件的处理顺序为先 less-loader 再 css-loader 再 vue-style-loader
use: [
// style-loader 会把 CSS 代码转换成字符串后,注入到 JavaScript 代码中去,
'vue-style-loader',
// css-loader 会找出 CSS 代码中的 @import 和 url() 这样的导入语句,告诉 Webpack 依赖这些资源。同时还支持 CSS Modules、压缩 CSS 等功能。处理完后再把结果交给 vue-style-loader 去处理。
{
loader: 'css-loader',
options: {
sourceMap: config.dev.cssSourceMap
}
},
//通过 less-loader 把 less 源码转换为 CSS 代码,再把 CSS 代码交给 css-loader 去处理。
{
loader: 'less-loader'
}
]
},
]
}
}
关于缩小范围增加命中这个思想,还可以做很多事情,这里只讲了vue脚手架优化做的事情,更多配置请往后看,看我如何自定义的
node 选项
webpack 的官方脚手架里面的node选项可以防止node包,还有 setImmediate 的 profill注入到代码中
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
好不好,看疗效。那么具体的疗效怎么样呢,楼主同样的代码,做了对比,效果如下:
通过对比可以看到,两次打包css的hash值全部变了,js部分hash发生改变(这个打包没看出js变化,但是另一个项目的部分js的hash变了)。总体打出来的包的体积相差不大。去掉node选项打包时间差别不明显,所以用不用,见仁见智吧。我看create-react-app中也使用了,所以还是建议使用吧,知道更多的,可以留言区讨论。
js、css 压缩
css 压缩这个就不多说了,大家都懂,
值得一提的是由于 UglifyJsPlugin 插件升级到1.0之后有了 parallel选项,开启了多线程压缩
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true // 开启多线程压缩
})
这两个插件都有配置项,合理配置可以优化项目。后面会讲。
代码分割
代码分割就是将动态引入的代码分割成一个一个的代码块(chunk),根据需求加载到html上。注意:要使用代码分割功能,在项目中要配合使用组件、路由懒加载的方式(可以通过import实现)
webpack4 的 mode 为 production 时,默认会对代码进行分割。楼主看了 webpack3 的代码分割方式是使用 CommonsChunkPlugin 插件,目的就是分割出几类代码:
- vendor 也就是第三方库打包这里。
- manifest 当编译器开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest"
- app 这个是代码中的公共部分
HashedModuleIdsPlugin
嗯 webpack 生成 js 的 hash 是如何计算我并不清楚,但是如果不用这个插件的话,所有生成 js 的 hash 是一样的,而且只要有一点点改动,所有文件的 hash 值都会变化。那造成什么样的结果呢?
比如你只改了 b 页面的 js 里的一行代码,如果不用此插件的话,所有页面的 js 的 hash 全部会变化,浏览器要重新请求全部的js。性能浪费到令人发指。而使用了 HashedModuleIdsPlugin 这个插件,只有你改动的那个 chunk 的 hash会发生变化,其他不变,由于浏览器的缓存机制,浏览器只重新请求改动的js。是不是很棒。而且上一小节对代码分割那里的分割方式,也是为了把不经常变动的文件单独打包,hash 可以保持不变。
使用方法也很简单
new webpack.HashedModuleIdsPlugin(),
什么?为什么就算去掉 HashedModuleIdsPlugin 插件 用脚手架第一次打包项目生成的 js 的 hash 不全部一样,而且改动之后,也不是全部发生变化啊。这个也是楼主遇到的问题。楼主不用脚手架搭建的项目,js 的 hash 是一样的,知道为什么出现初始打包的 js hash 值为什么不全部一样的同学,欢迎评论区讨论。
作用域提升(scope hoisting)
过去 webpack 打包时的一个取舍是将 bundle 中各个模块单独打包成闭包。这些打包函数使你的 JavaScript 在浏览器中处理的更慢。相比之下,一些工具像 Closure Compiler 和 RollupJS 可以提升(hoist)或者预编译所有模块到一个闭包中,提升你的代码在浏览器中的执行速度。
个插件会在 webpack 中实现以上的预编译功能。
new webpack.optimize.ModuleConcatenationPlugin()
这种连结行为被称为“作用域提升(scope hoisting)
记住,此插件仅适用于由 webpack 直接处理的 ES6 模块。在使用转译器(transpiler)时,你需要禁用对模块的处理(例如 Babel 中的 modules 选项)。
css 优化
由于css加载不会阻塞dom的解析,所以把css抽取出来。不占用js的大小是一个明智的选择 OptimizeCSSPlugin 插件做的就是这个,并且代码复用,会减小css体积
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
总结
总体来讲 webpack 为我们做的优化有
- babel-plugin-transform-runtime 插件去除重复垫片代码
- module.rules 的 js 解析,使用 include 提高命中
- node 选项,防止 node 的自带包(dgram、fs、net、tls、child_process)注入到我们的代码中
- js、css 压缩,代码分割,公共部分抽离
- 维持打包后不变chunk的hash值不变
- 作用域提升(scope hoisting)
- css 抽离。公共部分抽离
大致就这样了,有没有讲到的也请评论区提出,那么如何在此基础上做优化呢,这个也许是大家都很关心的问题。接下来我会在 《关于webpack优化,你需要知道的事(下篇)》讲到。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。