构建速度——记 Vue 项目中 Webpack 使用 DllPlugin
前言
使用 webpack 插件 DllPlugin 和 DllReferencePlugin ,是前端工程化中优化打包速度的重要途径。webpack中文网-DllPlugin。
他们可以打包常用的且不经常更新的模块,生成 JS 和 json文件,一般放入 public 目录中;项目打包时不会再对这些依赖进行编译,而是通过在 html 中插入 script 标签来读取依赖。比如 vue,antd,echarts 等常用框架和资源库。这在项目依赖包达到一定规模时尤为明显,在速度的提升上是显著的。
网上对于插件的使用介绍繁多,但少有对于 vue-cli 构建的项目对于此插件使用做出详细的说明,以及插件的坑和注意事项。我也是在工作中配置此插件时或多或少的问题,我想在以后的将来应该还会遇到诸类问题。
vue-cli 中 webpack
vue-cli 构建的项目,默认和最简单的是使用 vue.config.js 来配置 webpack,并且有自己独特的语法和规则。webpack相关|vue-cli。
插件的最关键部分在于 configureWebpack
选项提供的对象中(或函数方法)。按照文档,你需要基于环境有条件地配置行为,或者想要直接修改配置,按照函数方法 (该函数会在环境变量被设置之后懒执行)来使用。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象。
// vue.config.js
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
} else {
// 为开发环境修改配置...
}
}
}
DllPlugin 配置
一般专门使用一个新的配置文件用来生成依赖库文件。该插件一般会在项目中 public/vender 目录位置生成依赖文件。
配置
webpack.dll.config.js
// 定义常用对象
const path = require('path')
const webpack = require('webpack')
const CleanWebpackPlugin = require('clean-webpack-plugin')
// dll文件存放的目录
const dllPath = 'public/vendor'
module.exports = {
// 需要提取的库文件
entry: {
vue: ["vue", "vue-router", "vuex", 'axios'],
antd: ["ant-design-vue"],
echarts: ["echarts"],
},
output: {
path: path.join(__dirname, dllPath),
filename: '[name].dll.js',
// vendor.dll.js中暴露出的全局变量名
// 保持与 webpack.DllPlugin 中名称一致
library: '[name]_[hash]'
},
plugins: [
// 清除之前的dll文件
new CleanWebpackPlugin(['*.*'], {
root: path.join(__dirname, dllPath)
}),
// 定义插件
new webpack.DllPlugin({
path: path.join(__dirname, dllPath, '[name]-manifest.json'),
// 保持与 output.library 中名称一致
name: '[name]_[hash]',
context: process.cwd()
})
]
}
主要说明
- 定义常用对象
clean-webpack-plugin
主要用于每次生成动态链接库时首先清空 vendor 目录。
- dll 文件存放目录
一般定义为 public/vendor。
注意:一般将动态链接库放到项目的 public 目录下,而不要放在 dist 或其他目录中。
- entry 入口
定义提取哪些库/依赖。
在该对象中,键名定义生成生成文件的前缀;键值为数组类型,定义依赖名。例如在上述代码中,将 “vue 全家桶” 定义为 vue;那样的定义方法,会让插件将这些依赖生成为三个文件。同时会对应生成三个个名为 manifest.json 的文件来描述动态链接库包含了哪些内容,这个文件是用于让 DllReferencePlugin
能够映射到相应的依赖上。
- output 出口
path
选项使用 NodeJs 的 path 构建路径(绝对路径)。
filename
选项使用暴露出的 dll 的函数名作为文件前缀。
library
此插件与该选项相结合可以暴露出(也称为放入全局作用域)dll 函数,要与 Dllplugin 中的 name 一致,这里使用 hash 值以解决缓存和可能的重复。
- plugins
定义插件。
path
manifest.json 文件的 绝对路径(输出文件)。
name
暴露出的 DLL 的函数名(TemplatePaths:[fullhash] & [name] )。
context
(可选): manifest 文件中请求的 context (默认值为 webpack 的 context),这里使用 process.cwd()
返回当前工作目录。
DllReferencePlugin 配置
此插件配置在 webpack 的主配置文件中,此插件会根据描述文件引用依赖到需要的预编译的依赖中,避免在公共区域重复编译依赖。
配置
vue.config.js
module.exports = {
configureWebpack: config => {
plugins: [
...
// 避免在公共区域重复编译依赖
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(`./public/vendor/vue-manifest.json`)
})
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(`./public/vendor/antd-manifest.json`)
})
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(`./public/vendor/echarts-manifest.json`)
})
]
}
}
index.html 配置
在 index.html 中引入资源库js 文件。
index.html
<script src="/vendor/vue.dll.js"></script>
<script src="/vendor/antd.dll.js"></script>
<script src="/vendor/echarts.dll.js"></script>
注意:为避免在 Vue 路由的非根路径刷新页面导致报错,请使用绝对路径。
主要说明
-
context
(绝对路径) manifest (或者是内容属性)中请求的上下文,这里使用process.cwd()
返回当前工作目录。 -
manifest
包含 content 和 name 的对象,或者是一个字符串 —— 编译时用于加载 JSON manifest 的路径。
配置脚本命令使用
完成插件的配置。
在打包前运行脚本命令来生成动态资源库,通常情况下在很长一段时间内你只需运行一次,因为这些依赖并不会经常变动,且生成的资源库一般会上传到仓库。
配置
package.json
{
...
"scripts": {
"dll": "webpack --progress ./webpack.dll.config.js"
}
}
运行命令
$ npm run dll
技巧
使用 add-asset-html-webpack-plugin 自动引入资源库
当你的依赖足够多,生成的资源库文件众多时;或你对依赖进行增删时,或重新规划你的资源库名字时,一条一条的在 html 中引入显然不是明智之举。
使用 add-asset-html-webpack-plugin
插件可以帮你解决这个问题。
注意
援引该插件的官方文档说明,在迁移到 webpack 4+ 后,需要在 HtmlWebpackPlugin
之后应用该插件,目的是来注册一个钩子,而以前的 webpack 版本并不需要如此。
配置
vue.config.js
module.exports = {
configureWebpack: config => {
plugins: [
...
new HtmlWebpackPlugin({
title: 'My Project',
template: 'public/index.html',
favicon: 'public/logo.png'
})
new AddAssetHtmlPlugin({
// dll文件位置
filepath: path.resolve(__dirname, './public/vendor/*.js'),
// dll 引用路径,请使用 绝对路径!!!
publicPath: '/vendor',
// dll最终输出的目录
outputPath: './vendor'
})
]
}
}
publicPath
script 标签生成的 src 路径。如果你使用了 Vue 路由,在任何非根路径刷新页面,如果使用相对路径,都会导致资源库 js 文件路径前面拼接路由导致报错;因此此处必须使用绝对路径,切记!。
filepath
dll 文件的位置,配置 *.js
可以使插件加载目录下的所有资源库 js 文件。
注意:使用 HtmlWebpackPlugin
插件后,会根据模板重构页面,因此需要使用插件支持的暴露变量,原本的 BASE_URL
变量将会失效。使用 htmlWebpackPlugin.options.title
,且移除引用 favicon 的 link 标签。
index.html
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico"> -->
</head>
优化配置
DllReferencePlugin 插件配置时代码存在重复,进行优化。 在 webpack.dll.config.js 文件中我们其实可以拿到所有的 name,即可以对其进行遍历生成。
vue.config.js
const DllConfig = require('./webpack.dll.config')
module.exports = {
configureWebpack: config => {
let plugins = [
...
]
// 避免在公共区域重复编译依赖
Object.keys(DllConfig.entry).forEach(key=>{
plugins.push(new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(`./public/vendor/${key}-manifest.json`)
}))
})
// 整合插件
config.plugins = [...config.plugins, ...plugins]
}
}
注意问题
环境限制
不要在 开发环境 下使用 add-asset-html-webpack-plugin
,这会影响到该环境下的热加载(主要是因为它的前置插件 HtmlWebpackPlugin
热更新时更新所有页面)。具体原因大概是 webpack 的缓存导致:在触发热加载后,webpack 不会再重新读取配置文件,重新刷新页面后,无法在 html 中插入 script 标签导致页面直接报错。
script 标签的路径
在使用 Vue Router 时:
AddAssetHtmlPlugin
插件配置中的 publicPath
务必使用绝对路径,或直接在页面引入的 script 标签
也要使用绝对路径,否则会导致在非根路径刷新时导致页面报错。
这点在上面已作出说明和示例。
整合配置
一份整合的参考配置。
vue.config.js
const path = require('path')
const webpack = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const DllConfig = require('./webpack.dll.config')
module.exports = {
...
configureWebpack: config => {
let plugins = [
new HtmlWebpackPlugin({
title: 'My Project',
template: 'public/index.html',
favicon: 'public/logo.png'
})
]
if (process.env.NODE_ENV === 'production') {
// 将生成的 dll 文件注入到 生成的 html 模板中
plugins.push(new AddAssetHtmlPlugin({
// dll文件位置
filepath: path.resolve(__dirname, './public/vendor/*.js'),
// dll 引用路径
publicPath: '/vendor',
// dll最终输出的目录
outputPath: './vendor'
}))
// 避免在公共区域重复编译依赖
Object.keys(DllConfig.entry).forEach(key=>{
plugins.push(new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(`./public/vendor/${key}-manifest.json`)
}))
})
}
// 整合插件
config.plugins = [...config.plugins, ...plugins]
}
}
webpack.dll.config.js
// 定义常用对象
const path = require('path')
const webpack = require('webpack')
const CleanWebpackPlugin = require('clean-webpack-plugin')
// dll文件存放的目录
const dllPath = 'public/vendor'
module.exports = {
// 需要提取的库文件
entry: {
vue: ["vue", "vue-router", "vuex", 'axios'],
antd: ["ant-design-vue"],
echarts: ["echarts"],
},
output: {
path: path.join(__dirname, dllPath),
filename: '[name].dll.js',
// vendor.dll.js中暴露出的全局变量名
// 保持与 webpack.DllPlugin 中名称一致
library: '[name]_[hash]'
},
plugins: [
// 清除之前的dll文件
new CleanWebpackPlugin(['*.*'], {
root: path.join(__dirname, dllPath)
}),
// 定义插件
new webpack.DllPlugin({
path: path.join(__dirname, dllPath, '[name]-manifest.json'),
// 保持与 output.library 中名称一致
name: '[name]_[hash]',
context: process.cwd()
})
]
}
转载自:https://juejin.cn/post/6915028318900125703