一个项目的构建的性能优化,主要是从 构建时间层面
和 构建体积层面
这两个方面入手。
npm i webpack-bundle-analyzer -D
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
plugins: [
new BundleAnalyzerPlugin(),
]
npm i speed-measure-webpack-plugin -D
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
plugins: [
new SpeedMeasureWebpackPlugin(),
]
这个不多说了,新版本语言底层运行逻辑优化更佳。
thread-loader
/ HappyPack
这是一个多进程构建打包的 loader,能够极大提高构建的速度,只要将 thread-loader 放在构建耗时较大的 loader 之前,比如 babel-loader
npm i thread-loader -D
{
test: /.js$/,
use: [
'thread-loader',
'babel-loader'
],
}
esbuild 是基于 Go 语言开发的,而 esbuild-loader 则是在 esbuild 基础上增加的对 webpack 的支持适配,使得 webpack 能够利用 esbuild (Go 语言)的多线程能力,从而对构建流程进行优化提速。
使用 esbuild-loader 对 babel-loader 与 ts-loader 进行替换处理,甚至利用 esbuild 的多线程力量能将 thread-loader 也省略了引入了。
npm i esbuild-loader -D
const { ESBuildPlugin } = require('esbuild-loader')
{
test: /.js$/,
loader: 'esbuild-loader',
options: {
loader: 'jsx', // Remove this if you're not using JSX
target: 'es2015' // Syntax to compile to (see options below for possible values)
}
},
{
test: /.tsx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx', // Or 'ts' if you don't need tsx
target: 'es2015',
tsconfigRaw: require('./tsconfig.json')
}
},
plugins: [
new ESBuildPlugin()
]
将构建结果保存到文件系统中,在下次编译时对比每一个文件的内容哈希或时间戳,未发生变化的文件跳过编译操作,直接使用缓存副本,减少重复计算;发生变更的模块则重新执行编译流程。
webpack 5 已经整合相关 plugin,webpack 4 及更低的版本可使用 hard-source-webpack-plugin 插件
https://webpack.js.org/configuration/cache
module.exports = {
cache: {
type: 'filesystem'
},
};
这个 loader 能够缓存构建的资源,进而提高二次构建时候的速度,将 cache-loader 放在构建耗时较大的 loader 之前,比如 babel-loader、vue-loader。
npm i cache-loader -D
{
test: /.js$/,
use: [
'cache-loader',
'babel-loader'
],
},
{
test: /.vue$/,
use: ['cache-loader', 'vue-loader'],
},
这个仅仅需要在开发构建开启,真正上线生产构建时候需要关闭的功能。
在修改了项目中某一个文件,一般会导致整个项目刷新,这非常消耗构建的时间和 CPU 资源。如果是只刷新相关的修改代码范围的模块,其他保持原状,那能够极大的提高修改代码的重新构建时间。
const webpack = require('webpack');
{
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
}
合理的设置规定 webpack 处理编译范围,能够节约运行构建时候所处理的文件量进而对构建速度和时间进行优化。
缩小打包作用域:
{
test: /.js$/,
include: path.resolve(__dirname, '../src'),
exclude: /node_modules/,
use: [
'babel-loader'
]
},
区分环境去构建是非常重要的,我们要明确知道,开发环境时我们需要哪些配置,不需要哪些配置;而最终打包生产环境时又需要哪些配置,不需要哪些配置:
在webpack里,通过选择 development, production 或 none 之中的一个,来设置mode参数,从而可以启用webpack内置在相应环境下的优化,其默认值为 production。
「作用域提升」,它可以让 webpack 打包出来的「代码文件更小」,「运行更快」;webpack 将引入到 JS 文件“提升到”它的引入者的顶部。
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
// 方法1: 将 `mode` 设置为 production,即可开启
mode: "production",
// 方法2: 将 `optimization.concatenateModules` 设置为 true
optimization: {
concatenateModules: true,
usedExports: true,
providedExports: true,
},
// 方法3: 直接使用 `ModuleConcatenationPlugin` 插件
plugins: [new ModuleConcatenationPlugin()]
};
source-map 的作用是:方便你报错的时候能定位到错误代码的位置。但是它的体积不容小觑,所以对于不同环境设置不同的类型是很有必要的。
module.exports = {
devtool: 'eval',
};
开发环境 适合使用:
生产环境 适合使用:
样式代码从js文件中提取到单独的css文件中。
npm i mini-css-extract-plugin -D
const devMode = process.env.NODE_ENV !== 'production'
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// 这里可以指定一个 publicPath
// 默认使用 webpackOptions.output中的publicPath
// publicPath的配置,和plugins中设置的filename和chunkFilename的名字有关
// 如果打包后,background属性中的图片显示不出来,请检查publicPath的配置是否有误
publicPath: './',
hmr: devMode, // 仅dev环境启用HMR功能
},
},
'css-loader',
'sass-loader'
],
},
]
},
plugins: [
new MiniCssExtractPlugin({
// 这里的配置和webpackOptions.output中的配置相似
// 即可以通过在名字前加路径,来决定打包后的文件存在的路径
filename: devMode ? 'css/[name].css' : 'css/[name].[hash].css',
chunkFilename: devMode ? 'css/[id].css' : 'css/[id].[hash].css',
})
]
}
CSS 代码压缩使用 css-minimizer-webpack-plugin 插件,这个插件能够对 css 样式代码进行压缩、去重。
npm i css-minimizer-webpack-plugin -D
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
optimization: {
minimizer: [
new CssMinimizerPlugin(), // 去重压缩 css
],
}
npm i purgecss-webpack-plugin -D
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
plugins: [
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
webpack4中支持了零配置的特性,同时对块打包也做了优化,CommonsChunkPlugin 已经被移除了,现在是使用 optimization.splitChunks 代替。
设置 webpack 分包分割配置。
SplitChunksPlugin 默认只对 Async Chunk 生效,开发者也可以通过 optimization.splitChunks.chunks 调整作用范围,该配置项支持如下值:
缓存组的作用在于能为不同类型的资源设置更具适用性的分包规则
可以将 optimization.runtimeChunk 设置为 true,以此将运行时代码拆分到一个独立的 Chunk,实现分包。
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-vendor',
test: /[\/]node_modules[\/]/,
priority: 10,
},
corejs: {
name: 'chunk-corejs',
test: /[\/]node_modules[\/]_?(.*)core-js(.*)/,
priority: 15,
},
vue: {
name: 'chunk-vue',
test: /[\/]node_modules[\/]_?(.*)vue(.*)/,
priority: 15,
},
vant: {
name: 'chunk-vant',
test: /[\/]node_modules[\/]_?(.*)vant(.*)/,
priority: 20,
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
},
},
},
如果不进行模块懒加载的话,最后整个项目代码都会被打包到一个js文件里,单个 js 文件体积非常大,那么当用户网页请求的时候,首屏加载时间会比较长,使用模块懒加载之后,大js文件会分成多个小js文件,网页加载时会按需加载,大大提升首屏加载速度
const asyncChunk = () => import('...')
import(/* webpackChunkName: "async-lib" */ '...').then(component => {
console.log(component)
})
JS代码压缩使用 terser-webpack-plugin,实现打包后JS代码的压缩
npm i terser-webpack-plugin -D
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
minimizer: [
...
new TerserPlugin({ // 压缩JS代码
terserOptions: {
compress: {
drop_console: true, // 去除console
},
},
}), // 压缩JavaScript
],
}
多进程并行压缩
上面我们提及到使用 esbuild 能够替换 ts-loader、babel-loader 对 ts/js 文件进行处理,这里 esbuild-loader 也是能够进行 js 的代码压缩处理,并且因因为 esbuild 的多线程构建能力,性能速度方面也是不差的。
const {
ESBuildPlugin, ESBuildMinifyPlugin
} = require('esbuild-loader')
optimization: {
minimize: true,
minimizer: [
new ESBuildMinifyPlugin()
],
},
plugins: [
new ESBuildPlugin()
]
只打包用到的代码,没用到的代码不打包,而 webpack5 默认开启 tree-shaking,当打包的 mode 为 production 时,自动开启 tree-shaking 进行优化。
module.exports = {
mode: 'production'
}
npm i image-webpack-loader -D
{
test: /.(jpg|png|gif|bmp)$/,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
}
}
}
]
}
开启Gzip后,大大提高用户的页面加载速度,因为gzip的体积比原文件小很多,当然需要运维端 nginx / tomcat 的配置设置支持返回。
npm i compression-webpack-plugin -D
const CompressionPlugin = require('compression-webpack-plugin')
plugins: [
new CompressionPlugin({
algorithm: 'gzip',
threshold: 10240,
minRatio: 0.8
})
]
使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
npm i html-webpack-externals-plugin -D
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
plugins: [
new HtmlWebpackPlugin(),
new HtmlWebpackExternalsPlugin(
externals: [
{
module: 'jquery',
entry: 'dist/jquery.min.js',
global: 'jQuery',
},
{
module: 'jquery',
entry: 'https://unpkg.com/[email protected]/dist/jquery.min.js',
global: 'jQuery',
},
],
),
]
当 webpack 打包引入第三方模块的时候,每一次引入,它都会去从 node_modules 中去分析,这样肯定会影响 webpack 打包的一些性能,如果我们能在第一次打包的时候就生成一个第三方打包文件,在接下去的过程中应用第三方文件的时候,就直接使用这个文件,这样就会提高 webpack 的打包速度。
使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: {
react: ["echarts"]
},
output: {
path: path.resolve(__dirname, "./dll"),
filename: "dll_[name].js",
library: 'dll_[name]'
},
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false
})
]
},
plugins: [
new webpack.DllPlugin({
name: "dll_[name]",
path: path.resolve(__dirname, "./dll/[name].manifest.json")
})
]
}
plugins: [
new webpack.DllReferencePlugin({
context: resolveApp("./"),
manifest: resolveApp("./dll/echarts.manifest.json")
}),
new HtmlWebpackExternalsPlugin ({
externals: [
{
module: 'echart',
entry: resolveApp('./dll/dll_echarts.js'),
global: 'echarts',
},
],
})
],
改过的文件需要更新hash值,而没改过的文件依然保持原本的hash值,这样才能保证在上线后,浏览器访问时没有改变的文件会命中缓存,从而达到性能优化的目的。
output: {
path: path.resolve(__dirname, '../dist'),
// 给js文件加上 contenthash
filename: 'js/chunk-[contenthash].js',
clean: true,
},
主要有两种方式:
// 访问url,根据 User Agent 直接返回浏览器所需的 polyfills
https://polyfill.io/v3/polyfill.js
对于一些小图片转成 base64 格式并且整合进 js 文件当中能够有效的减少用户的http网络请求次数,提高用户的体验。
url-loader
/ asset-module
{
test: /.(png|jpe?g|gif|svg|webp)$/,
type: 'asset',
parser: {
// 转base64的条件
dataUrlCondition: {
maxSize: 25 * 1024, // 25kb
}
},
generator: {
// 打包到 image 文件下
filename: 'images/[contenthash][ext][query]',
},
},
原文链接:前端工程化之 Webpack 5 项目构建优化