prefacio
En el desarrollo diario lo que más se nota es el tiempo de compilación y empaquetado del proyecto, sobre todo cuando se empaqueta y despliega con más frecuencia, este tiempo es muy largo.
Por ejemplo: el proceso de compilación tomó alrededor de 86 segundos para "arrancar en frío" un proyecto que se tomó antes:
¿Se puede tolerar esto? Obviamente no, por lo que planeo optimizarlo, de lo contrario, afectará demasiado la experiencia de desarrollo. El siguiente es un procesamiento realizado en el proceso de optimización.
Analice los módulos que consumen mucho tiempo
Ver la configuración integrada de vue-cli
El usuario vue-cli-service inspect
puede ver fácilmente el contenido de configuración incorporado de vue-cli , ingrese: en la terminal , y el archivo vue inspect --mode production webpack.config.production.js
proyecto y src .webpack.config.production.js
herramienta de análisis
Para obtener módulos que consumen mucho tiempo, debe utilizar algunas herramientas de análisis, como:
velocidad-medida-webpack-plugin
Este complemento puede medir la velocidad de compilación de los paquetes web y generar el tiempo de compilación de cada módulo, lo que puede ayudarnos a encontrar mejor los módulos que consumen mucho tiempo.
Configurar en vue.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
module.exports = {
...,
configureWebpack: (config) => {
...
config.plugins.push(
new SpeedMeasurePlugin(),
);
},
};
复制代码
Reiniciar
A partir de los resultados de salida en la figura a continuación, es fácil ver los módulos correspondientes que consumen mucho tiempo.
analizador de paquete webpack
Este complemento visualiza el tamaño de los archivos de salida del paquete web y proporciona un mapa de árbol interactivo con zoom.
Configurar en vue.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
+ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
...,
configureWebpack: (config) => {
...
config.plugins.push(
new SpeedMeasurePlugin(),
+ new BundleAnalyzerPlugin()
);
},
};
复制代码
Reiniciar
A partir de los resultados de salida en la figura a continuación, puede observar fácilmente el tamaño empaquetado de cada módulo.
mejoramiento
cargador de subprocesos: habilita la optimización de subprocesos múltiples
De acuerdo con el tiempo de compilación generado por speed-measure-webpack-plugin , el tiempo de compilación de los siguientes cargadores es relativamente grande:
- vue-loader
- cargador ts
- cargador de babel
- image-webpack-cargador
- postcss-loader
关于这一部分就可以通过 thread-loader 进行优化,因为它能将这些非常耗时的内容单独放到另一个线程中执行,但并不是针对所有的 loader 都做这个处理,因为这个处理本身也是有较大的开支。
注意:仅在耗时的操作中使用 thread-loader,否则使用 thread-loader 会后可能会导致项目构建时间变得更长,因为每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右,同时还会限制跨进程的数据交换等。
在回顾下前面,没有使用 thread-loader 前项目的 "冷启动" 时间约 86
秒:
只针对 babel-loader 使用 thread-loader 后项目的 "冷启动" 时间约 78
秒:
在对其他 laoder 使用 thread-loader 发现时间更长,因此取尝试后的最优结果
hard-source-webpack-plugin —— 使用缓存优化
webpack 中几种缓存方式:
cache-loader
hard-source-webpack-plugin
babel-loader 的 cacheDirectory 标志
以上这些缓存方式都有首次启动时的开销,即它们会让 "冷启动" 时间会更长,但是二次启动能够节省很多时间.
而 vue-cli 已经内置了 cache-loader
和 babel-loader 的 cacheDirectory 标志
,其中对应的配置如下:
默认情况下的 "二次启动" 时间约为 38 秒,原因是 "冷启动" 时已经将 babel-laoder、ts-loader、vue-loader
进行了缓存:
先配置 hard-source-webpack-plugin 插件:
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+ const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
...,
configureWebpack: (config) => {
...
config.plugins.push(
// 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
// 解决未检测到的配置更改
+ new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [],
environmentHash: {
root: process.cwd(),
directories: [],
// 配置了files 的主要原因是解决配置更新,cache 不生效了的问题
// 配置后有包的变化,plugin 会重新构建一部分 cache
files: ['package.json', 'yarn.lock']
}
}),
new SpeedMeasurePlugin(),
new BundleAnalyzerPlugin(),
);
},
};
复制代码
使用 hard-source-webpack-plugin 后的 "冷启动" 和 "二次启动" 时间如下:
减少打包体积
减少 js 代码体积
在打包之后的 dist 目录下,查找对应的 console.log,结果如下:
可以发现 vue-cli 中默认的配置并没有将 js 文件中的一些 console.log 语句进行删除,因此这也是一些可以继续优化的内容.
这里可以通过 uglifyjs-webpack-plugin
或 terser-webpack-plugin
插件来删除注释和压缩 js 代码,具体配置可以直接点链接进行查阅.
对图片压缩
针对一些对图片像素没有很高要求的图片资源进行压缩,这里可以通过 image-webpack-plugin
或 image-minimizer-webpack-plugin
进行图片资源的压缩,具体配置可以直接点链接进行查阅.
外部扩展 —— externals & cdn
externals 选项就是用于 防止 将某些 import
的包(package) 打包到 bundle 中,而是在运行时(runtime) 再去从外部获取这些 扩展依赖(external dependencies),具体可见
简单来说就是原本应该要被打包到 bundle 中的 js,现在通过配置 externals 选项,将它做外 bundle 之外的资源,即 cdn 资源,在代码运行时再去请求这个资源。
例如,下面就是在项目关于 externals 的配置:
chainWebpack: (config) => {
...
// 通过 CDN 方式引入资源
config.externals({
echarts: 'echarts',
nprogress: 'NProgress',
});
}
复制代码
DllPlugin 插件 —— 优化打包时间
DllPlugin 插件负责将那些比较稳定(例如 vue/react 全家桶)的库进行打包拆分 bundles,下次在打包时就不需要重复进行打包,因此大幅度提升了构建的速度。
webpack4 版本中,已经集成 DllPlugin 插件,我们只需要进行配置即可,具体可见
- 创建
dll.js
文件,进行简单配置
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
vendor: ['echarts', 'element-ui', 'vue/dist/vue.esm.js', 'vue-router', 'vuex'],
},
output: {
path: path.join(__dirname, 'target'),
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
new webpack.DllPlugin({
// DllPlugin的name属性需要和libary保持一致
name: '[name]_[hash]',
//指定当前目录
path: path.join(__dirname, '.', '[name]-manifest.json'),
// context需要和webpack.config.js保持一致
context: __dirname,
}),
],
};
复制代码
- 在
package.json
文件中配置script
脚本:"dll": "webpack --config ./dll.js"
- 安装
webpack-cli
,因为原本依赖中并没有安装过这个依赖,而运行这个脚本命令需要webpack-cli
- 执行脚本命令
npm run dll
,生成vendor-manifest.json
文件,这个文件是用于让DllReferencePlugin
能够映射到相应的依赖上
- 在
vue.config.js
中配置DllReferencePlugin
插件,链接到已被打包的依赖上
const { pathResolve } = require('./build/utils.js'); // eslint-disable-line
const devConfig = require('./build/webpack.dev.conf.js'); // eslint-disable-line
const buildConfig = require('./build/webpack.prod.conf.js');
// 分析工具
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 资源缓存
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// 抽离稳定的第三方库,避免重复打包
const DllReferencePlugin = require('webpack').DllReferencePlugin;
// 公共函数
const { versionSet } = require('./build/utils'); // eslint-disable-line
// 是否为开发环境
const isDevelopment = process.env.NODE_ENV == 'development';
const vueWebpackConfig = () => {
let envConfig = {};
if (isDevelopment) {
// 开发
envConfig = devConfig;
} else {
// 构建
versionSet();
envConfig = buildConfig;
}
const vueConfig = {
// 环境配置
...envConfig,
productionSourceMap: isDevelopment, // 是否在构建生产包时生成sourcdeMap
// 拓展webpack配置
chainWebpack: (config) => {
// ============ 配置别名 ============
config.resolve.alias
.set('@build', pathResolve('../build')) // 构建目录
.set('@', pathResolve('../src'))
.set('@api', pathResolve('../src/api'))
.set('@utils', pathResolve('../src/utils'))
.set('@views', pathResolve('../src/views'));
// ============ svg处理 ============
const svgRule = config.module.rule('svg');
// 清除已有的所有 loader。
// 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。
svgRule.uses.clear();
// 添加要替换的 loader
svgRule.use('svg-sprite-loader').loader('svg-sprite-loader').options({
symbolId: 'icon-[name]',
});
// ============ 压缩图片 ============
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
.end();
// ============ 打包分析工具 ============
if (!isDevelopment) {
if (process.env.npm_config_report) {
config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin).end();
config.plugins.delete('prefetch');
}
}
// ============ CDN资源引入 ============
config.externals({
// echarts: 'echarts',
nprogress: 'NProgress',
});
},
configureWebpack: (config) => {
// 尽量保证项目中文件后缀的精确
config.resolve.extensions = ['.ts', '.js', '.vue', '.json'];
// 处理 babel-loader
config.module.rules[12].use.unshift({
loader: 'thread-loader',
});
config.plugins.push(
// 为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [],
environmentHash: {
root: process.cwd(),
directories: [],
// 配置了files 的主要原因是解决配置更新,cache 不生效了的问题,配置后有包的变化,plugin 会重新构建一部分cache
files: ['package.json', 'yarn.lock'],
},
}),
// DllReferencePlugin 插件
new DllReferencePlugin({
context: __dirname,
// manifest就是我们第 2 步中打包出来的 json 文件
manifest: require('./vendor-manifest.json'),
}),
// 分析工具
new SpeedMeasurePlugin(),
new BundleAnalyzerPlugin(),
);
},
};
return vueConfig;
};
module.exports = vueWebpackConfig();
复制代码
其他优化
resolve.alias & resolve.extensions
resolve.alias 是用于创建 import
或 require
的别名,来确保模块引入变得更简单
例如,下面是项目中定义的一些别名:
chainWebpack: (config) => {
// 配置别名
config.resolve.alias
.set('@build', pathResolve('../build')) // 构建目录
.set('@', pathResolve('../src'))
.set('@api', pathResolve('../src/api'))
.set('@utils', pathResolve('../src/utils'))
.set('@views', pathResolve('../src/views'));
}
复制代码
resolve.extensions 指定为对应的文件后缀,保证在查找模块时的无用查找和递归等,即保证这个配置里的文件后缀要尽可能少。
减少不必要的解析 —— module.noParse
module.noParse 是防止 webpack 解析那些任何与给定正则表达式相匹配的文件,忽略的文件中 不应该含有 import
, require
, define
的调用,或任何其他导入机制,通过忽略大型的 library 可以提高构建性能。
例如,vue-cli 中关于 module.noParse 的配置如下:
代码层面优化
从 webpack-bundle-analyzer 反应的内容,可以发现某些 js 和 css 模块体积相对来说比较大,这个时候可以找到对应的文件梳理逻辑并进行代码优化,如封装 js 逻辑、抽离 css 样式等,即最基本的优化就是少书写重复的样式和逻辑,这样也能避免一些无效的重复编译.
最后
Las anteriores son algunas optimizaciones basadas en vue-cli (basadas en webapck), si no ha realizado la optimización correspondiente antes, puede probarla.
Finalmente, echemos un vistazo a los cambios en el tiempo de construcción del empaque de producción antes y después de la optimización: