webpack5高级优化
提升开发体验
Source-Map
是一个用来生成源代码和构建后代码一一映射的文件的方案。
// 开发模式 cheap-module-source-map 打包编译速度快 只有行映射 没有列映射
// 生产模式 source-map 包含行/列映射 打包编译速度更慢
module.exports = {
devtool: 'cheap-module-source-map'
}
提升打包构建速度
HotModuleReplacement
开发时我们修改了其中一个模块代码,webpack默认会将所有模块全部重新打包编译,速度很慢。
所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。
HotModuleReplacement(HMR) 在程序运行时,替换、添加或删除模块,而无需重新加载整个页面。
module.exports = {
devServer: {
host: 'localhost', //启动服务器的域名
port: 3000,
open: true, // 是否自动打开浏览器,
hot: true
}
}
// js文件要单独处理 实际开发中可使用其他loader 比如 react-hot-loader
if (module.hot) {
module.hot.accept('./src/index.js')
}
OneOf
打包时每个文件都会经过所有loader处理,虽然因为test正则原因实际没有处理上,但是都要过一遍,比较慢。
oneOf是只能匹配上一个loader,剩下的就不匹配了。
module.exports = {
module: {
rules: [
{
oneOf: [
]
}
]
}
}
Include/Exclude
include/exclude只能选一个配置
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// include: path.resolve(__dirname, 'src'), 只处理src
use: {
loader: 'babel-loader',
options: { // 可以写在babel.config.js
presets: ['@babel/preset-env']
}
}
}
]
}
}
Cache
每次打包时js文件都要经过Eslint和Babel编译,速度比较慢。
我们可以缓存之前的Esline和Babel编译结果,这样第二次打包时速度就会更快了。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// include: path.resolve(__dirname, 'src'), 只处理src
use: {
loader: 'babel-loader',
options: { // 可以写在babel.config.js
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存压缩 节省压缩时间
}
}
}
]
},
plugins: [
new EsLintPlugin({
context: path.resolve(__dirname, 'src'),
exclude: 'node_modules',
cache: true,
cacheLocation: path.resolve(__dirname, 'node_modules/.cache/eslintcache')
})
]
}
多进程打包
js文件处理主要就是eslint、babel、Terser三个工具,所以我们要提升他们的运行速度。
我们可以开启多进程同时处理js文件。
多进程打包:开启电脑的多个进程同时做一件事,速度更快。请在特别耗时的操作中使用,因为每个进程启动就有大约600ms左右开销。
我们启动进程的数量就是我们cpu的核数
- 获取cpu核数,每个电脑都不一样
const os = require('os');
const threads = os.cpus().length;
- 下载包 npm i thread-loader -D
- 配置
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// include: path.resolve(__dirname, 'src'), 只处理src
use: [
{
loader: 'thread-loader',
works: threads,
},
{
loader: 'babel-loader',
options: { // 可以写在babel.config.js
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存压缩 节省压缩时间
}
}
]
}
]
},
plugins: [
new ESLintPlugin({
// 检测哪些文件
context: path.resolve(__dirname, 'src'),
exclude: 'node_modules', // 默认值
cache: true,
cacheLocation: path.resolve(__dirname, 'node_modules/.cache/eslintcache'),
threads,
}),
// new CssMinimizerWebpackPlugin(),
// new TerserWebpackPlugin({
// parallel: threads
// })
],
optimization: {
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin({
parallel: threads
})
]
},
}
减少代码体积
Tree Shaking
开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。
如果没有特殊处理的话我们打包时会引入整个库,但是实际上我们可能只用上极小部分的功能,这样将整个库都打包进来,体积就太大了。
tree shaking 是一个术语,通常用于描述移除javascript中没有使用上的代码。webpack默认开启此功能。
减少Babel生成文件的体积
Babel为编译的每个文件都插入了辅助代码,使代码体积过大。
Babel为一些公共方法使用了非常小的辅助代码,比如 _extend,默认情况下会被添加到每一个需要它的文件。可以将这些代码作为独立模块,避免重复引入。
@babel/plugin-transform-runtime: 禁用了babel自动对每个文件的runtime注入,而是引入@babel/plugin-transform-runtime,并且使所有辅助代码从这里引用。
{
loader: 'babel-loader',
options: { // 可以写在babel.config.js
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存压缩 节省压缩时间
plugins: ["@balel/plugin-transform-runtime"],
}
}
Image Minimizer
image-minimizer-webpack-plugin: 用来压缩图片的插件
优化代码运行性能
Code Split
打包代码时会将所有js打包到一个文件中,体积太大了。如果我们只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载。
所以我们需要将打包生成的文件进行代码分割,生成多个js文件,渲染哪个页面就只加载某个js文件,这样加载的资源就少,速度就更快。
代码分割主要做了2件事:
- 分割文件:将打包生成的文件进行分割,生成多个js文件。
- 按需加载,需要哪个文件就加载哪个文件。
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'static/js/main.js',
chunkFilename: 'static/js/[name].chunk.js',
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:10].css',
chunkFilename: 'static/css/[name].chunk.[contenthash:10].css',
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: { // 单入口文件不需要配置
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
// 一个文件改变,contenthash更新,所有依赖他的文件都会跟着变化,缓存失效
// 把hash值保存在一个runtime文件
runtimeChunk: (entrypoint) => `runtime~${entrypoint.name}.js`
}
}
}
// count.js 会被单独打包成一个文件
import(/* webpackChunkNmae: "count" */'./count.js').then((res => res.default()))
// eslintrc.js 文件中增加 plugins: ['import']
Preload/Profetch
我们前面已经做了代码分割,同时会使用import动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。
但是加载速度还不够好,比如:是用户点击按钮才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。
我们想在浏览器空闲时间,加载后续要使用的资源,我们就需要用上Preload, Prefetch技术。
- Preload: 告诉浏览器立即加载资源
- Prefetch: 告诉浏览器空闲时间加载资源
- 共同点:都只会加载资源,并不执行。都有缓存
- 区别:
- Preload加载优先级更高
- Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。
- 总结:
- 当前页面优先级高的资源用Preload加载
- 下一个页面需要使用的资源用Prefetch加载
- 兼容性较差
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')
module.exports = {
plugins: [
new PreloadWebpackPlugin({
// rel: 'preload',
// as: 'script',
rel: 'prefetch',
})
]
}
core-js
解决js兼容性问题
import "core-js/es/promise"
// barbel.config.js 实现按需加载
module.exports = {
preset: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
}
]
]
}
PWA
开发webapp项目,项目一旦处于网络离线情况,就没法访问了。
我们希望给项目提供离线体验。
渐进式网络应用程序(progressive web application): 是一种可以提供类似于native app体验的webapp技术,其中最重要的是在离线时应用程序能够继续运行功能。
内部通过 Service Workers 技术实现的。
// npm i workbox-webpack-plugin
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
plugins: [
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
})
]
}
// main.js中注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}