本章以 vue-cli 2.x
生成的项目结构为例。
缩小文件搜索范围
Webpack 启动后会从配置的 Entry 出发,解析出文件中的导入语句,再递归的解析。 在遇到导入语句时 Webpack 会做两件事情:
- 根据导入语句去寻找对应的要导入的文件。例如 require(‘react’) 导入语句对应的文件是 ./node_modules/react/react.js, require(’./util’) 对应的文件是 ./util.js。
- 根据找到的要导入文件的后缀,使用配置中的 Loader 去处理文件。例如使用 ES6 开发的 JavaScript 文件需要使用 babel-loader 去处理。
- 优化
loader
的配置
在loader
中可以使用test
,include
,exclude
来命中loader
要应用规则的文件。所以为了提高构建速度,我们要尽可能少和准确的让文件被loader
处理。
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: resolve('src')
}
]
resolve.alias
配置别名
在vue-cli
中,默认会有一个vue$
和@
的别名,对我们构建形成直接影响的使vue$
,这里直接指向的是vue/dist/esm.js
,这样就减少了递归解析的操作。
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
}
resolve.extensions
配置后缀名
module.exports = {
resolve: {
// 尽可能的减少后缀尝试的可能性 以及 把需要优先匹配的放到前面
extensions: ['js', 'css']
},
}
使用DllPlugin
大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。 由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。
- 在
build
文件夹下新建一个webpack.dll.config.js
文件。
const path = require('path')
const webpack = require('webpack')
const AssetsPlugin = require('assets-webpack-plugin') // 生成带有资源路径的json文件
module.exports = {
entry: {
// 把这些依赖的包打包生成一个 libs开头 的文件夹,也可以更细粒度的拆分
libs: [
'vue/dist/vue.esm.js',
'vue-router',
'vuex',
'element-ui',
'moment',
'lodash',
'qs',
'axios',
'vue-amap',
'ant-design-vue'
]
},
output: {
path: path.resolve(__dirname, '../static'),
filename: '[name].[chunkhash:7].js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, '../static/[name]-manifest.json'),
name: '[name]_library', // 这里需要和 output.library 保持一致
context: __dirname
}),
new AssetsPlugin({
filename: 'bundle-config.json',
path: './static'
})
],
optimization: {
minimize: true
}
}
- 在
package.json
中添加一条命令
"scripts": {
"build:dll": "webpack --config build/webpack.dll.config.js"
}
然后执行
yarn build:dll
这样会在 static 中生成 bundle.config.json
,libs-manifest.json
,libs.[hash:7].js
三个文件。
- 在
webpack.dev.config.js
中加上一个插件
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('../static/libs-manifest.json')
})
]
- 这里还需要把生成后的文件引入到我们的
html
中,利用html-webpack-plugin
来动态解决
修改webpack.prod.conf.js
文件
plugins: [
new HtmlWebpackPlugin({
...
libJsName: bundleConfig.libs.js
})
]
- 在
index.html
中修改,这里是内置的ejs
语法。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="shortcut icon" href="static/favicon.icon" type="image/icon" />
<title>项目</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="./static/<%= htmlWebpackPlugin.options.libJsName %>"></script>
</body>
</html>
然后重新打包,即可。
HappyPack 开启多进程
文件读写和计算操作是无法避免的,那能不能让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力,以提升构建速度呢?
HappyPack 就能让 Webpack 把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
分解任务和管理线程的事情 HappyPack 都会帮你做好,你所需要做的只是接入 HappyPack。
yarn add happypack -D
const HappyPack = require('happypack')
module.exports = {
modules: {
rules: [
{
test: /\.js$/,
loader: 'happypack/loader?id=babel',
include: resolve('src')
}
]
},
plugins: [
new HappyPack({
id: 'babel', // 默认是 '1'
loaders: ['babel-loader?cacheDirectory'] // 为了尽可能地优化 开启 babel 的缓存
})
]
}
使用ParallelUglifyPlugin来多进程进行代码压缩
webpack
中内置的是 UglifyJs
来压缩代码,但是它是一个一个地去压缩,很耗费时间。
而 ParallelUglifyPlugin
是开启多个进程来并行压缩代码,内部还是使用 UglifyJs
来处理。
yarn add webpack-parallel-uglify-plugin -D
然后在 webpack.pro.config.js
中修改即可
const UglifyJsParallelPlugin = require('webpack-parallel-uglify-plugin')
module.exports = {
plugins: [
new UglifyJsParallelPlugin({
cacheDir: '.cache/', // 开启缓存
uglifyJS:{
output: {
comments: false
},
compress: false
}
})
]
}
在通过 new ParallelUglifyPlugin() 实例化时,支持以下参数:
- test:使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/,也就是默认压缩所有的 .js 文件。
- include:使用正则去命中需要被 ParallelUglifyPlugin 压缩的文件。默认为 []。
- exclude:使用正则去命中不需要被 ParallelUglifyPlugin 压缩的文件。默认为 []。
- cacheDir:缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回。cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。
- workerCount:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
- sourceMap:是否输出 Source Map,这会导致压缩过程变慢。
- uglifyJS:用于压缩 ES5 代码时的配置,Object 类型,直接透传给 UglifyJS 的参数。
- uglifyES:用于压缩 ES6 代码时的配置,Object 类型,直接透传给 UglifyES 的参数。
使用 externals 减少应用的依赖
当我们使用 jQuery
或者 vue
等时,代码量很大,打包起来比较耗时,那我们也可以使用 script
引用。
在 webpack.base.config.js
中修改
module.exports = {
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter'
}
}
然后在 index.html
中修改
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>title</title>
<script src="https://cdn.bootcss.com/vue/2.5.15/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
这时,还需要手动在模块引入的地方进修引入修改
在 src/main.js
中
// import Vue from 'vue'
const Vue = require('vue')
在 src/router
中
// import Router from 'vue-router'
// import Vue from 'vue'
// Vue.use(Router)
const Router = require('vue-router')
考虑是否提取CSS
我们知道,我们最后打包生成的 bundle
中,可以在 js
中来动态引用样式,也可以单独生成一个 css
文件来引入。这两种方案各有千秋,通常我们希望在生产环境中提取 css
,这样当我们修改 js
的时候,不会影响 css
的 contenthash
,使得我们的静态资源能够尽可能地长缓存。但是需要的深究一点的是,如果我们的 css
很小,也被抽离成单独的一个 css
文件,那就意味还需要发一次 http
请求,这样是不合理的。遗憾的是 mini-css-extract-plugun
现阶段还不支持 min-size
的功能。如果未来实现了该功能,我们就可以指定 min-size
来决定是需要提取 css
还是需要内联到 js
中。
现阶段我个人觉得还是不需要提取 css
。因为通常来说我们的 css
文件都还是很小,而一次 http
请求是相当昂贵的。我们可以在 webpack.pro.config.js
中修改
把 extract
改为 false
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: false,
usePostCSS: true
})
},
剩下的诸如 Tree-Shaking
,Scope-Hoisting
,Code-Splitting
和 提取公共代码
等就不赘述了,之前的章节都提到过。