上一章我们介绍了Webpack常用配置和大致优化思路,这一章节我们来看一下具体怎么优化(未完待续,大佬们可以在评论中提点意见)
git地址:github.com/jxs7754/dem…
1. 构建速度优化
- 高版本的node和Webpack
- 开启多进程,加快解析、压缩速度
- 分包,分离基础包
- 利用缓存来提升二次构建速度
- 减少文件搜索范围
速度分析:使用speed-measure-webpack-plugin
const SpeedMeasureWebpackPlugin = reqire('speed-measure-webpack-plugin');
const smp = new SeedMeasureWebpackPlugin();
const webpackCofig = smp.wrap({
plugins:[
// MyPlugin(),
]
})
可以分析整个打包的总耗时,可以查看每个loader和plugins的耗时情况;
1.1 使用高版本的Node和Webapck
- V8引擎的升级优化
- webpack4 默认使用更快md4 hash算法
- webpacks AST 可以直接从 loader 传递给 AST,减少解析时间
- 使用字符串方法替代正则表达式
1.2 开启多进程
thread-loader
{
module:{
rules: [
{
test: '/.js$/',
use: [
{
loader: 'thread-loader',
options:{
workers: 3,
}
},
'babel-loader'
]
}
]
}
}
HappyPack(作者已经不再维护)
const HappyPack = require('happypack');
exports.module = {
rules: [
{
test: /.js$/,
// 1) replace your original list of loaders with "happypack/loader":
// loaders: [ 'babel-loader?presets[]=es2015' ],
use: 'happypack/loader',
include: [ /* ... */ ],
exclude: [ /* ... */ ]
}
]
};
exports.plugins = [
// 2) create the plugin:
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
loaders: [ 'babel-loader?presets[]=es2015' ]
})
];
多线程压缩
// terser-webpack-plugin
module.exports = {
optimization: {
minimizer: {
new TerserPlugin({
parallel: 4,
})
}
}
}
// 下面这俩个插件可以配置多线程
// parallel-uglify-plugin
// uglifyjs-webpack-plugin
1.3 分包
设置Externals,使用 html-webpack-externals-plugin将基础包(vue vue-router)通过CDN,不打入包中。
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: 'https://xxx/react.min.js',
global: 'React',
},
{
module: 'react-dom',
entry: 'https://xxx/react-dom.min.js',
global: 'ReactDOM',
},
],
}),
没有CDN的情况 可以预编译 DllPlugin进行分包,DllReferencePlugin对manifest.json 引用
// 分包
module.exports = {
mode: 'production',
entry: {
vue: ['vue/dist/vue.esm.js', 'vue-router', 'vuex'],
axios: ['axios', 'qs'],
// ui: ['element-ui'],
},
output: {
filename: '[name]_[chunkhash:8].dll.js',
path: path.join(__dirname, 'build'),
library: '[name]',
},
plugins: [
new CleanWebpackPlugin(),
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.join(__dirname, 'build/[name].json'),
}),
],
};
// 引用
module.exports = {
plugins: [
...['vue', 'axios'].map((item) => new webpack.DllReferencePlugin({
context: path.join(__dirname, './build'),
manifest: require(`./build/${item}.json`),
})),
]
}
1.4 缓存
缓存是为了二次构建时候,加快构建
babel-loader 开启缓存
{
loader: 'babel-loader',
options:{
cacheDirectory: true
}
}
terser-webpack-plugin 开启缓存
{
optimization: {
minimizer: {
new TerserPlugin({
// 多线程
parallel: 4,
// 缓存
cache: true,
})
}
}
}
hard-source-webpack-plugin 或者 cache-loader
1.5 减少文件搜素范围
优化loader配置
由于 Loader 对文件的转换操作很耗时,所以需要让尽可能少的文件被 Loader 处理。可以通过 test/include/exclude 三个配置项来命中 Loader 要应用规则的文件。
使用合理的alias
在实战项目中经常会依赖一些庞大的第三方模块,以 Vue 库为例,发布出去的 Vue 库中包含多套代码, vue.runtime.esm.js 中只包含运行时的代码。如果不用template选项可以直接用这个减少打包体积。
module.exports = {
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.esm.js',
}
}
}
优化resolve.modules配置
resolve.modules 的默认值是['node_modules'],含义是先去当前目录的node_modules目录下去找我们想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules中找,以此类推。当安装的第三方模块都放在项目根目录的 node_modules 目录下时,就没有必要按照默认的方式去一层层地寻找,可以指明存放第三方模块的绝对路径,以减少寻找。
module.exports = {
resolve: {
modules: [path.resolve( __dirname,'node modules')]
}
}
优化resolve.mainFields配置
在安装的第三方模块中都会有一个package.json文件,用于描述这个模块的属性,其中可以存在多个字段描述入口文件,原因是某些模块可以同时用于多个环境中,针对不同的运行环境需要使用不同的代码。 segmentfault.com/a/119000001…
优化resolve.extensions配置
在导入语句没带文件后缀时,Webpack会自动带上后缀去尝试询问文件是否存在。如果这个列表越长,或者正确的后缀越往后,就会造成尝试的次数越多,所以resolve.extensions的配置也会影响到构建的性能在配置resolve.extensions时需要遵守以下几点,以做到尽可能地优化构建性能。
- 后缀列表尽可能小
- 频率出现高的文件后缀优先放前面
- 源码中写导入语句时,尽可能带上后缀
{
extensions: ['.js'],
},
2. 构建体积优化
- 提取公共代码、分割代码、按需加载、懒加载
- tree-shaking
- scope-hoisting
- 删除无用的css
- 动态polyfill
- 代码压缩,开启Gzip压缩
体积分析 webpack-bundle-analyzer
可以分析依赖的第三方模块的大小、业务里面组件的代码大小
const BoundAnalysisPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BoundAnalysisPlugin(),
]
}
2.1 提取公共代码、分割代码、按需加载、懒加载
// 组件按需加载
import {Button} from 'element-ui';
// 模块按需加载
import {cloneDeep} from 'lodash-es';
// Vue 路由懒加载
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
minRemainingSize: 0,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
2.2 tree-shaking
1个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle ,没用到的方法会在 uglify 阶段被擦除掉。 注意事项:
- mode: production 默认开启 babel设置 modules:false,
- 必须使用ES6的语法
2.3 删除无用的css
使用 purgecss-webpack-plugin 配合 mini-css-extract-plugin 使用
const config = {
module:{
rules: [
{
test: '/.scss$/',
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
// 自动扩展css
require('autoprefixer')(),
],
},
},
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: getAssetPath(
`css/[name]_[contenthash:8]'}.css`,
),
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
}
2.4 动态polyfill
| 方案 | 优点 | 缺点 |
|---|---|---|
| babel-polyfill | 大而全 | 体积太大 |
| @babel/plugin-transform-runtime | 只polyfill用到的方法和类,体积较小 | 不能polyfill原型上的方法 |
| polyfill-service | 只返回客户需要的polyfill | 国内奇葩浏览器 |
2.5 Scope-Hoisting
ModuleConcatenationPlugin 现在webpack4在mode 不等于none都支持
2.6 图片压缩,代码压缩,还可以开启Gzip压缩
使用 image-webpack-loader进行图片压缩
3. 加载优化
3.1 预加载
使用 @vue/preload-webpack-plugin 实现代码预加载
const config = {
plugins: [
new PreloadPlugin({
rel: 'preload',
include: 'initial',
fileBlacklist: [/\.map$/, /hot-update\.js$/],
}),
new PreloadPlugin({
rel: 'prefetch',
include: 'asyncChunks',
}),
]
}
3.2 使用文件指纹,浏览器缓存
- Hash:和整个项⽬的构建相关,只要项⽬文件有修改,整个项⽬构建的 hash 值就会更改
- Chunkhash:和 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值
- Contenthash:根据文件内容来定义 hash ,文件内容不不变,则 contenthash 不不变
// js
{
output: {
filename: '[name]_[chunkhash:8].js'
}
}
// css
// MiniCssExtractPlugin
{
plugins:[
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
})
]
}
// 图片
// file-loader 使用hash(这里的hash是根据内容生成的,默认是md5)
{
module:{
rules:[
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader: 'file-loader’,
options: {
name: 'img/[name][hash:8].[ext] '
}
}]
}
]
}
}
。。。未完待续