webpack开发环境优化
热更新
module.exports = {
devServer:{
hot:true // 开启HMR
},
plugins:[
// 可选
new webpack.HotModuleReplacementPlugin()
]
}
加快打包速度
🍊SWC
- SWC 代替 babel
- 全称是 Speedy Web Compiler,它是一个使用 Rust 编写的编译器。
- swc 特点:
- 高性能
- 兼容性
- 生态系统
- 用 swc 替换 babel 之后,能够获取到的性能上面的收益:
- 编译速度
- 多线程处理
- 内存管理
🍊 使用 thread-loader
- thread-loader 通过创建多个子线程,并行处理文件,从而减少主线程的负载,加速整个编译过程。
- 为loader的运行开启多线程
thread-loader会开启一个线程池,线程池中包含适量的线程- 它会把后续的loader放到线程池的线程中运行,以提高构建效率
- 由于后续的loader会放到新的线程中,所以,后续的loader不能:
- 使用 webpack api 生成文件
- 无法使用自定义的 plugin api
- 无法访问 webpack options
在实际的开发中,可以进行测试,来决定
thread-loader放到什么位置
🍊 利用 webpack5 的持久化缓存技术
持久化缓存技术是 webpack5 引入的新技术,可以对构建内容进行缓存:
- memory:缓存在内存中,适用于开发环境
- filesystem:以文件的形式缓存在磁盘上,适用于生产环境
一般缓存的内容:
- 模块缓存
- 解析缓存
- 插件缓存
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
// ...
},
module: {
// ..
},
cache: {
type: 'filesystem', // 使用文件系统进行缓存
cacheDirectory: path.resolve(__dirname, '.webpack_cache'), // 缓存目录
buildDependencies: {
config: [__filename], // 当配置文件改变时,重新构建缓存
},
name: 'my-cache', // 缓存名称
version: '1.0', // 缓存版本
},
};
🍊 发环境去掉 hash
在 webpack 配置中,hash 的目的是为了生成唯一的文件名:bundle.(hash).js
不同环境下对 hash 的需求是不一样的:
- 开发环境:频繁进行代码修改和构建,不需要长时间缓存,生成 hash 会增加不必要的构建时间。
- 生产环境:希望生成 hash,以便利用浏览器缓存机制,提高加载速度。
因此在 webpack 配置文件里面,就可以动态的配置是否要生成 hash:
const path = require('path');
module.exports = (env, argv) => {
// 获取当前的构建模式
const isProduction = argv.mode === 'production';
return {
entry: './src/index.js',
output: {
// 根据不同的构建模式来决定生成的文件名是否要包含 hash 值
filename: isProduction ? 'bundle.[contenthash].js' : 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.m?js$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
},
},
],
},
cache: {
type: 'filesystem',
},
};
};
在 package.json 中可以配置启动模式:
{
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
}
}
🍊 升级老旧的 plugin
例如升级 terser-webpack-plugin,这个插件使用了压缩代码的,新版本相比旧版本就有很大的提升:
- 性能改进:
-
- 算法优化:插件的新版本通常包含更高效的算法和优化策略,可以在保持相同压缩率的同时加快压缩速度。
- 多线程处理:新版本可能引入了对多线程的支持,从而利用多核 CPU 提升压缩性能。
- Bug 修复和改进:
-
- 修复性能瓶颈:老版本可能存在一些未被发现的性能瓶颈或 bug,通过升级可以避免这些问题。
- 代码改进:维护者和社区贡献者会不断地改进插件的代码,以提高其性能和稳定性。
- 新特性:
-
- 缓存支持:新版本支持持久化缓存功能,从而避免重复压缩相同的代码块,进一步提升构建速度。
- 配置优化:简化和改进配置选项,使得更容易进行性能调优。
减少模块解析
什么叫做模块解析?
模块解析包括:抽象语法树分析、依赖分析、模块语法替换
不做模块解析会怎样?
如果某个模块不做解析,该模块经过loader处理后的代码就是最终代码。
如果没有loader对该模块进行处理,该模块的源码就是最终打包结果的代码。
如果不对某个模块进行解析,可以缩短构建时间
哪些模块不需要解析?
模块中无其他依赖:一些已经打包好的第三方库,比如jquery
module.exports = {
mode: "development",
devtool: "source-map",
module: {
noParse: /node_moudules/
}
}
优化loader性能
进一步限制loader的应用范围
思路是:对于某些库,不使用loader
例如:babel-loader可以转换ES6或更高版本的语法,可是有些库本身就是用ES5语法书写的,不需要转换,使用babel-loader反而会浪费构建时间
lodash就是这样的一个库
通过module.rule.exclude或module.rule.include,排除或仅包含需要应用loader的场景
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /lodash/,
use: "babel-loader"
}
]
}
}
如果暴力一点,甚至可以排除掉node_modules目录中的模块,或仅转换src目录的模块
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
//或
// include: /src/,
use: "babel-loader"
}
]
}
}
这种做法是对loader的范围进行进一步的限制,和noParse不冲突,想想看,为什么不冲突
缓存loader的结果
我们可以基于一种假设:如果某个文件内容不变,经过相同的loader解析后,解析后的结果也不变
于是,可以将loader的解析结果保存下来,让后续的解析直接使用保存的结果
cache-loader可以实现这样的功能
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['cache-loader', ...loaders]
},
],
},
};
有趣的是,cache-loader放到最前面,却能够决定后续的loader是否运行
实际上,loader的运行过程中,还包含一个过程,即pitch
cache-loader还可以实现各自自定义的配置,具体方式见文档
特别注意,开启和管理线程需要消耗时间,在小型项目中使用thread-loader反而会增加构建时间
webpack生产环境优化
分包
手动分包
和自动分包比较
- 优点: 构建速度快
- 缺点: 相比于自动分包, 麻烦, 降低开发效率
🍊 自动分包
- 抽离公共代码, 合理使用缓存
- 将react-dom, antd, 这些第三方库抽离出来
- 这些第三方库的代码基本上是不会变的
- 将这些第三方库缓存在本地
- 谁然第一次加载慢,但之后由于缓存的原因,加载就会很快了
module.exports = {
optimization: {
splitChunks: {
// 分包策略
}
}
}
🍊 css拆分
- 将css代码从js中抽离出来
- 打包成一个独立的css文件
- MiniCssExtractPlugin.loader作用: 生成 link 标签
- style-loader作用: 生成 style 标签
- 既然css已经被抽离到了一个单独的文件,所以就不需要style标签了,而是link标签
- new MiniCssExtractPlugin()作用: 单独生成一个css文件
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
chunks: ["index"]
}),
new MiniCssExtractPlugin({
filename: "[name].[hash:5].css",
// chunkFilename是配置来自于分割chunk的文件名
chunkFilename: "common.[hash:5].css"
})
]
}
css分包
- 多页面应用会拆分出多个css文件
- css分包就是提取出这些文件的公共样式到一个单独的文件中,从而减少代码体积
module.exports = {
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
styles: {
test: /\.css$/, // 匹配样式模块
minSize: 0, // 覆盖默认的最小尺寸,这里仅仅是作为测试
minChunks: 2 // 覆盖默认的最小chunk引用数
}
}
}
},
module: {
rules: [{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] }]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
chunks: ["index"]
}),
new MiniCssExtractPlugin({
filename: "[name].[hash:5].css",
// chunkFilename是配置来自于分割chunk的文件名
chunkFilename: "common.[hash:5].css"
})
]
}
🍊 懒加载
- 打包成一个异步的chunk
const btn = document.querySelector("button");
btn.onclick = async function() {
//动态加载
//import 是ES6的草案
//浏览器会使用JSOP的方式远程去读取一个js模块
//import()会返回一个promise (* as obj)
// const { chunk } = await import(/* webpackChunkName:"lodash" */"lodash-es");
const { chunk } = await import("./util");
const result = chunk([3, 5, 6, 7, 87], 2);
console.log(result);
};
react中
import { lazy } from 'react';
// import Editor from '../question/editor';
const Editor = lazy(() => import('../question/editor'));
代码压缩
🍊 Terser或者UgilyfyJs
- 例如: 把代码变成一行
- UgilyfyJs: 不支持es6语法
- Terser: 支持es6语法,webpack内置Teser
webpack自动集成了Terser
如果你想更改、添加压缩工具,又或者是想对Terser进行配置,使用下面的webpack配置即可
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
optimization: {
// 是否要启用压缩,默认情况下,生产环境会自动开启
minimize: true,
minimizer: [ // 压缩时使用的插件,可以有多个
new TerserPlugin(),
new OptimizeCSSAssetsPlugin()
],
},
};
🍊 three shaking
-
使用
export xxx导出,而不使用export default {xxx}导出 -
使用
import {xxx} from "xxx"导入,而不使用import xxx from "xxx"导入
🍊 gzip
CmpressionWebpackPlugin 用于对构建后的 .js 文件进行压缩,test: /.js/ 表示只对 .js 文件应用压缩,而 minRatio: 0.5 表示只有文件大小减少一半或以上时才进行压缩。
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CmpressionWebpackPlugin = require("compression-webpack-plugin")
module.exports = {
mode: "production",
optimization: {
splitChunks: {
chunks: "all"
}
},
plugins: [
new CleanWebpackPlugin(),
new CmpressionWebpackPlugin({
test: /\.js/,
minRatio: 0.5
})
]
};