前言
Vue-Cli中内置了Webpack,但是配置文件和Webpack也不尽相同。
我们可以通过命令查看对应的Webpack配置。
对于优化主要是两个方面
- 构建速度
- 打包体积
所以不管是分析问题还是解决问题有围绕这连个方面进行处理。
Vue-Cli自带
- cache-loader 会默认为
Vue/Babel/TypeScript
编译开启。文件会缓存在node_modules/.cache
中。 如果你遇到了编译方面的问题,记得先清缓存目录之后再试试看。 - thread-loader 会在多核 CPU 的机器上为
Babel/TypeScript
转译开启。
查看Vue-Cli中的Webpack配置
介绍
Vue-Cli脚手架会有webpack的很多默认行为,因此我们得知道基于Vue-Cli的项目,当前的webpack都配置了啥,然后才能做针对性的分析与优化。
vue-cli-service
暴露了inspect
命令用于审查解析好的 webpack 配置。那个全局的vue
可执行程序同样提供了inspect
命令,这个命令只是简单的把vue-cli-service inspect
代理到了你的项目中。
使用方式:
代码语言:javascript复制#根据mode,分别生成开发环境、生产环境的配置
vue inspect --mode production > webpack.config.production.js
vue inspect --mode development > webpack.config.development.js
输入命令后,在根目录会生产一个webpack.config.production.js
文件
如果vue command not found
的错可以全局安装注册一下vue命令
npm install -g vue-cli
分析
查看构建时间
说明:https://www.npmjs.com/package/speed-measure-webpack-plugin
安装
代码语言:javascript复制npm install --save-dev speed-measure-webpack-plugin
使用
代码语言:javascript复制const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
module.exports = {
chainWebpack: config => {
config
.plugin('speed-measure-webpack-plugin')
.use(SpeedMeasurePlugin)
.end();
}
}
或者
代码语言:javascript复制const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
module.exports = {
configureWebpack: (config) => {
config.plugins.push(
new SpeedMeasurePlugin(),
);
},
};
查看构建库大小
VUE CLI内置工具
代码语言:javascript复制vue-cli-service build --report
成功后就会在项目目录下找到/dist/report.html
结果如下图所示:
如果报错
node_modules.binvue-cli-service.ps1,因为在此系统上禁止运行脚本。
执行之后就能运行上面的命令了
代码语言:javascript复制set-ExecutionPolicy RemoteSigned -Scope CurrentUser
#查看执行权限
Get-ExecutionPolicy
webpack-bundle-analyzer
Vue CLi就不用这个工具了,但是也可以配置,配置后运行项目打开项目页面的同时也会打开分析页面。
安装
代码语言:javascript复制npm install --save-dev webpack-bundle-analyzer
配置
Webpack配置
代码语言:javascript复制const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
Vue Cli配置
代码语言:javascript复制const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin()
]
}
}
优化
构建速度优化
happypack
安装
代码语言:javascript复制npm install --save-dev happypack
配置
代码语言:javascript复制/*
happypack
*/
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
configureWebpack: config => {
return {
/* happypack */
module: {
rules: [
{
test: /.js$/,
loader: 'happypack/loader?id=happyBabel',
exclude: /node_modules/
},
]
},
externals: {
vue: "Vue",
"vue-router": "VueRouter",
vuex: "Vuex",
echarts: "echarts",
axios: "axios",
},
plugins: [
new HappyPack({
id: 'happyBabel',
loaders: [{
loader: 'babel-loader?cacheDirectory=true',
}],
//共享进程池
threadPool: happyThreadPool,
//允许 HappyPack 输出日志
verbose: true,
})
],
}
},
}
实测没啥效果
vue-cli-plugin-dll
代码语言:javascript复制npm install --save-dev vue-cli-plugin-dll
接下来就是dll的相关配置,将我们项目中的依赖使用dll插件进行动态链接,这样依赖就不会进行编译,从而极大地提高编译速度,因为这些插件没有编译,在vue.config.js
中进行配置,也很简单
const path = require("path");
module.exports = {
pluginOptions: {
dll: {
//这里放的是你的依赖插件,就是你项目安装的其他的插件,将这些插件的名字依次加在后面,我建议将所有项目依赖插件全部放在后面
//注意这里不能放webpack,gulp等需要node环境的插件,我尝试将babel等放到这里报错提示没有V8环境
entry: ["vue", "vue-router", "view-design"],
//dll 编译后的链接库的地址
cacheFilePath: path.resolve(__dirname, "./public"),
// 是否开启 DllReferencePlugin,
open: true,
// 在执行 `dev` , `build` 等其他指令时,程序会自动将 `dll` 指令生成的 `*.dll.js` 等文件自动注入到 index.html 中。
inject: true
}
},
}
多入口
代码语言:javascript复制const path = require('path')
module.exports = {
pluginOptions: {
dll: {
// 入口配置
entry: {
vue: ["vue", "vue-router", "vuex"],
ui: ["view-design"],
},
// 输出目录
output: {
path: path.join(__dirname, 'public/dll'),
filename: '[name].dll.js',
// vendor.dll.js中暴露出的全局变量名
// 保持与 webpack.DllPlugin 中名称一致
library: '[name]_[hash]'
},
// 是否开启 DllReferencePlugin,
open: true,
// 在执行 `dev` , `build` 等其他指令时,程序会自动将 `dll` 指令生成的 `*.dll.js` 等文件自动注入到 index.html 中。
inject: true,
}
}
}
配置好之后然后运行,进行你上面配置插件动态链接库的编译
代码语言:javascript复制npx vue-cli-service dll
dll编译完成后会在上面配置的目录下生成dll文件夹,就可以开始跑项目了,因为这些插件都不需要编译,跑起来很流畅,修改后的热更新速度更是显著提升。我以前修改一行代码热更新编译在30秒以上,使用这个以后基本十秒以内搞定。
代码语言:javascript复制npm run serve
多线程优化
Vue-Cli
Vue-Cli已经内置,开启
代码语言:javascript复制module.exports = {
parallel: true,
}
parallel
- Type:
boolean
- Default:
require('os').cpus().length > 1
是否为 Babel 或 TypeScript 使用thread-loader
。 该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
Webpack
代码语言:javascript复制npm install --save-dev thread-loader
配置
代码语言:javascript复制const path = require("path");
module.exports = {
module: {
rules: [
{
test: /.js$/,
include: path.resolve('src'),
use: [
"thread-loader",
// 耗时的 loader (例如 babel-loader)
],
},
],
},
};
缓存构建
Webpack 中几种缓存方式:
cache-loader
hard-source-webpack-plugin
以上这些缓存方式都有首次启动时的开销,即它们会让 “冷启动” 时间会更长,但是二次启动能够节省很多时间.
而 Vue-Cli 已经内置了 cache-loader
进行以下两个的缓存了
- babel-loader 的 cacheDirectory 标志
- vue-loader 的 cacheDirectory 标志
所以
Vue Cli没有必要添加
HardSourceWebpackPlugin
HardSourceWebpackPlugin
详细说明
https://www.npmjs.com/package/hard-source-webpack-plugin
在启动项目时会针对项目生成缓存,若是项目无package或其他变化,下次就不用花费时间重新构建,直接复用缓存。
安装
代码语言:javascript复制npm install --save-dev hard-source-webpack-plugin
配置vue.config.js
为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
configureWebpack: config => {
config.plugin.push(
// 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [],
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package.json', 'yarn.lock']
}
})
// 配置了files的主要原因是解决配置更新,cache不生效了的问题,配置后有包的变化,plugin会重新构建一部分cache
)
}
}
或者
代码语言:javascript复制const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
configureWebpack: {
plugins:[
new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [],
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package.json', 'yarn.lock']
}
})
]
}
}
更多配置
代码语言:javascript复制// 缓存 加速二次构建速度
new HardSourceWebpackPlugin({
cacheDirectory: "../node_modules/.cache/hard-source/[confighash]",
recordsPath:
"../node_modules/.cache/hard-source/[confighash]/records.json",
configHash: function(webpackConfig) {
return require("node-object-hash")({ sort: false }).hash(
webpackConfig
);
},
// Either false, a string, an object, or a project hashing function.
environmentHash: {
root: process.cwd(),
directories: [],
files: ["../package-lock.json", "../yarn.lock"]
},
// An object.
info: {
// 'none' or 'test'.
mode: "none",
// 'debug', 'log', 'info', 'warn', or 'error'.
level: "debug"
},
// Clean up large, old caches automatically.
cachePrune: {
// Caches younger than `maxAge` are not considered for deletion. They must
// be at least this (default: 2 days) old in milliseconds.
maxAge: 2 * 24 * 60 * 60 * 1000,
// All caches together must be larger than `sizeThreshold` before any
// caches will be deleted. Together they must be at least this
// (default: 50 MB) big in bytes.
sizeThreshold: 100 * 1024 * 1024
}
})
缩小文件检索解析范围
为避免无用的检索与递归遍历,可以使用alias指定引用时候的模块,noParse,对不依赖本地代码的第三方依赖不进行解析。
Vue-Cli默认已进行了如下配置
代码语言:javascript复制noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/
配置
代码语言:javascript复制// 定义getAliasPath方法,把相对路径转换成绝对路径
const getAliasPath = dir => join(__dirname, dir)
module.exports = {
configureWebpack: config => {
config.module.noParse = /^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios|view-design)$/
}
chainWebpack: config => {
// 添加别名
config.resolve.alias
.set('@', getAliasPath('src'))
.set('assets', getAliasPath('src/assets'))
.set('utils', getAliasPath('src/utils'))
.set('views', getAliasPath('src/views'))
.set('components', getAliasPath('src/components'))
}
// 生产环境禁用eslint
lintOnSave: !process.env.NODE_ENV !== 'production',
}
或者
代码语言:javascript复制// vue.config.js
module.exports = {
configureWebpack:{
module: {
noParse: /^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios|view-design)$/
}
}
}
import优化
运用这个插件能在代码使用了import语法的情况下,大大提高代码的编译速度。
安装 babel-plugin-dynamic-import-node
npm install --save-dev babel-plugin-dynamic-import-node
vue-cli3
修改babel.config.js文件
代码语言:javascript复制module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
env: {
development: {
plugins: ["dynamic-import-node"]
}
}
};
vue.cli2
.babelrc文件
代码语言:javascript复制"env": {
"test": {
"plugins": []
},
"development":{
"presets": ["env", "stage-2"],
"plugins": ["dynamic-import-node"]
}
}
打包体积优化
不生成SourceMap
production环境不生成SourceMap
代码语言:javascript复制module.exports = {
lintOnSave: false,
productionSourceMap: process.env.NODE_ENV !== "production", //打包不生成map文件
}
图片压缩
image-webpack-plugin
对图片像素要求没很极致的,这个压缩还是可以使用的,压缩率肉眼看起来感觉是没太大区别。 这里注意一下,我没有对svg进行压缩,原因是压缩的svg,再通过构建时被打包成base64时,生成的base64会有问题,无法访问。
代码语言:javascript复制module.exports = {
chainWebpack: config => {
// 对图片进行压缩
config.module
.rule('images')
.test(/.(png|jpe?g|gif)(?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
.end()
}
}
gzip压缩
使用 gzip 压缩代码,效果显著。
安装
代码语言:javascript复制npm install compression-webpack-plugin
配置
代码语言:javascript复制const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
configureWebpack: config => {
// 生产环境下生效
if (process.env.NODE_ENV === 'production') {
// 配置 gzip 压缩
config.plugins.push(
new CompressionWebpackPlugin({
test: /.js$|.html$|.css$/,
threshold: 4096 // 超过4kb压缩
})
)
}
}
}
配置CDN
老实说,我不用这个功能的,线上使用 cdn 总让我有一种不安全感,除非公司有自己的 cdn 库,不过这确实也是一种优化方案,效果也还不错。它的配置也很简单,在 externals 中配置,例子:
代码语言:javascript复制module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 配置 cdn,这里将 vue,vue-router 和 axios 三个包配置成 cdn 引入
// 其中 Vue,VueRouter 等名称是该库暴露在全局中的变量名
config.externals = {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios'
}
}
}
}
然后在 public/index.html
模板文件中引入 cdn 地址:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title></title>
<!-- 引入 cdn 地址 -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.10/vue.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.18.0/axios.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
我这里使用的是 bootcdn 的地址,需要注意版本问题。
也可以借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入。
使用 cdn 引入的方式虽然能极大改善网页加载速度,但我还是不会用这个功能,项目还不需要非得这样的优化,也怕 cdn 不稳定。
当然
也可以不用CDN,直接把JS复制到项目下,用相对路径引用即可。
借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入
代码语言:javascript复制//生产环境标记
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const path = require("path");
// 生产配置
const cdn_production = {
js: ["/librarys/vue@2.6.11/vue.min.js"]
};
// 开发配置
const cdn_development = {
js: ["/librarys/vue@2.6.11/vue.js"]
};
module.exports = {
configureWebpack: {
externals: {
vue: "Vue",
},
},
chainWebpack: config => {
config.plugin("html").tap(args => {
args[0].cdn = IS_PRODUCTION ? cdn_production : cdn_development;
return args;
});
}
};
index.html中添加
代码语言:javascript复制<% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
Vue Cli配置说明
https://cli.vuejs.org/zh/guide/webpack.html
简单的配置方式
调整 webpack 配置最简单的方式就是在 vue.config.js
中的 configureWebpack
选项提供一个对象:
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [
new MyAwesomeWebpackPlugin()
]
}
}
该对象将会被 webpack-merge 合并入最终的 webpack 配置。
链式操作 (高级)
Vue CLI 内部的 webpack 配置是通过 webpack-chain 维护的。这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件,并有机会在后期进入这些规则并对它们的选项进行修改。
它允许我们更细粒度的控制其内部配置。接下来有一些常见的在 vue.config.js
中的 chainWebpack
修改的例子。
提示
当你打算链式访问特定的 loader 时,vue inspect 会非常有帮助。
修改 Loader 选项
代码语言:javascript复制// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(
options => {
// 修改它的选项...
return options
})
}
}
提示
对于 CSS 相关 loader 来说,我们推荐使用 css.loaderOptions 而不是直接链式指定 loader。这是因为每种 CSS 文件类型都有多个规则,而 css.loaderOptions
可以确保你通过一个地方影响所有的规则。