原稿:语雀 · webpack-vue_3:优化 · 原目录:Webpack › Demo › Webpack 搭建 Vue 项目 › webpack-vue_3:优化
这篇完全基于上一篇的构建结果,大家随意 DIY,了解常用的 webpack 优化手段
参考链接:三十分钟掌握Webpack性能优化
优化构建速度
首先大致总结 webpack 的打包过程
- webpack 打包过程会从 entry point 出发,递归地解析 modules 间的依赖,同时构建构建依赖图
- webpack 将不同的模块合并为 chunk,chunk 还能够合并成 chunk 组
- 当必需的模块都被加载和编译完毕后,webpack 会输出 bundle 文件,其中包含了经过加载和编译的最终源文件,可以直接在浏览器中运行(关于module、chunk、bundle 的区分:webpack 中,module,chunk 和 bundle 的区别是什么?)
以上大致分为搜索过程与加载编译过程,优化也可以从这两方面入手
指定搜索范围
这里主要配置 webpack resolve,告诉 webpack 从哪里寻找模块、排除哪些地方,从而提高模块搜索的效率。实际上在 webpack-vue_2 中引入 vue 时已经使用到了这种优化方式:
![]() |
|
![]() |
|
使用 dll/cache 减少基础模块编译次数
- 使用 DllPlugin 可以为更改不频繁的代码生成单独的编译结果,提高编译速度。其原理是把网页依赖的基础模块抽离出来打包到 dll 文件中,当需要导入的模块命中 dll 时,这个模块不再被打包,而是去dll中获取。在 4.x 版本后 webpack 打包性能提升,诸如 vue-cli 都没有再使用 dll。dll 配置些许麻烦,这里暂不使用了。
- HardSourceWebpackPlugin 插件和 dll 思想一致,不过实现不同:它为模块提供中间缓存步骤,首次打包速度正常,之后打包则会比对 cache 中新旧 configHash,在 webpack5 中已经内置了开箱即用的 cache,因此不用额外引入该插件(而且亲测 5.x 版本时候后会报错)
使用 HappyPack 开启多进程
运行在Node.js之上的 Webpack 是单线程模型的,不能并行处理文件,HappyPack 可以将任务分解给多个工作者线程,最后将结果发给主线程,通过这种多线程的方式实现并行编译(官方文档)
npm install --save-dev happypack
在配置中引入插件,同时修改 rule,需要和 happypack 中的 id 吻合
plugins: [
//...
new HappyPack({
id: "babel",
loaders: ["babel-loader?cacheDirectory"],
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ["happypack/loader?id=babel"],
},
]
}
之后进行 build,实际测试,构建速度不减反增!因为在小型项目中,使用 happypack 进行调度的开销大于对编译过程的优化,因此甚至会增加项目的构建速度,只推荐在复杂项目中使用(参考链接:使用 happypack 提升 Webpack 项目构建速度),插件 thread-loader 与之类似
优化输出
区分当前环境
development(开发环境) 和 production(生产环境) 环境下的构建目标存在着巨大差异
- 开发环境:强大的 source map、能实时重载的 localhost server
- 生产环境:压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善前端加载时间
这代表不同环境下的配置必然有区别,webpack-merge 插件允许为不同环境编写独立的配置,同时提供公共配置
npm install --save-dev webpack-merge
删除原有的 webpack.config.js,创建三个新的配置文件,对应公共、开发、生成环境
// webpack.common.js
// 留下共用的插件;删除devTool、devServer项
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 引入插件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
mode: "development",
entry: "./src/main.js",
plugins: [
new HtmlWebpackPlugin({
title: "Webpack Vue",
template: "./public/index.html",
}),
new CleanWebpackPlugin(),
new VueLoaderPlugin(),
],
module: { /* 未改变 */ },
resolve: { /* 未改变 */},
};
// webpack.dev.js// 指定mode;填写devTool、devServer项const { merge } = require("webpack-merge");const common = require("./webpack.common.js");module.exports = merge(common, { mode: "development", devtool: "inline-source-map", devServer: {/* 未改变 */}, },});
// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
})
之后修改 npm script,npm run build 用于构建生产版本,npm run serve 构建开发环境版本
"scripts": {
"build": "webpack --config webpack.prod.js",
"serve": "webpack serve --config webpack.dev.js"
},
代码压缩
压缩JS:Webpack 内置 UglifyJS 插件、ParallelUglifyPlugin 开启并行压缩
UglifyJS 会分析 JS 语法树,理解代码的含义,从而做到去掉无效代码、去掉日志输入代码、缩短变量名等优化。常用配置参数如下:
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
//...
plugins: [
new UglifyJSPlugin({
compress: {
warnings: false, //删除无用代码时不输出警告
drop_console: true, //删除所有console语句,可以兼容IE
collapse_vars: true, //内嵌已定义但只使用一次的变量
reduce_vars: true, //提取使用多次但没定义的静态值到变量
},
output: {
beautify: false, //最紧凑的输出,不保留空格和制表符
comments: false, //删除所有注释
}
})
]
压缩 ES6:uglifyjs-webpack-plugin
压缩源文件中的 js 代码,且该插件提供了压缩 ES6 代码的功能
压缩 CSS:CssMinimizerWebpackPlugin
该插件使用了 cssnano,不仅是删掉空格,还能理解代码含义,例如把 color:#ff0000 转换成 color:red
使用 Tree-shaking 剔除冗余代码
Tree Shaking 可以剔除未被引用的 dead code,它依赖 ES6 模块语法的静态结构特性(在导入、导出语句中的路径必须是静态字符串,且不能放入其他代码块中),适合用于Lodash、utils.js等工具类较分散的文件。
具体使用见:Tree Shaking
使用 externals 配置常用库
打包后的 bundle 可以在真正运行时才去获取外部依赖,此时构建后的包体积就会明显缩小,提升构建和加载速度。webpack externals 配置选项提供了从输出的 bundle 中排除依赖的方法,而是在运行时获取。
<script
src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous">
</script>
典型的场景就是如上通过 CDN 引入 jQuery,此时不应该再打包模块,因此在 externals 中声明这是一个外部扩展
// webpack.config.js
module.exports = {
//...
externals: {
jquery: 'jQuery'
}
};
在其他模块中正常使用扩展即可
import $ from 'jquery';
$('.my-element').animate(/* ... */);
优化前端性能
注意,上一节的区分环境、代码压缩、tree-shaking、externals 都可优化前端性能,只不过这些优化大多在打包完成输出后就完成,而本节的优化方案侧重于前端访问时利用缓存机制、懒加载机制提高加载速度
提取公共代码
大型网站通常由多个页面组成,每个页面都是一个独立的单页应用,多个页面间肯定会依赖同样的样式文件、技术栈等。如果不把这些公共文件提取出来,那么每个单页打包出来的 bundle 中都会包含公共代码,相当于要传输 n 份重复代码。如果把公共文件提取出一个文件,那么当用户访问了一个网页,加载了这个公共文件,再访问其他依赖公共文件的网页时,就直接使用文件在浏览器的缓存,这样公共文件就只用被传输一次。
常见的提取公共代码实现: 2. 入口分离
文档给出了一个场景,分离 app 和 vendor 的入口,前者可能被频繁的维护,而后者可以是的第三方库或文件,长期不会更新,如此分割后,每次打包生成的 app bundle 哈希值会更改,浏览器会拉取最新的内容;而独立的 vendor bundle 文件名不变,浏览器可使用缓存,减少了加载时间

