优化构建速度
- 优化resolve配置
- alias:用的创建 import 或 require 的别名,用来简化模块引用 ```javascript const path = require(‘path’) // 路径处理方法 function resolve(dir){ return path.join(__dirname, dir); }
const config = { resolve:{ // 配置别名 alias: {
'~': resolve('src'),
'@': resolve('src'),
'components': resolve('src/components'),
} } } // 使用 src 别名 ~ import ‘~/fonts/iconfont.css’
- 优化resolve配置
// 使用 src 别名 @ import ‘@/fonts/iconfont.css’
// 使用 components 别名 import footer from “components/footer”
- **extensions**:按照配置的数组从左到右的顺序去尝试解析模块;高频文件后缀名放前面;手动配置后,默认配置会被覆盖
```javascript
const config = {
resolve: {
extensions: ['.js', '.json', '.wasm']
# 想保留默认配置,可以用 ... 扩展运算符代表默认配置
// extensions: ['.ts', '...']
}
}
# 引入模块时不带扩展名
import file from '../path/to/file'
- **modules:**告诉 webpack 解析模块时应该搜索的目录
const path = require('path')
// 路径处理方法
function resolve(dir){
return path.join(__dirname, dir)
}
const config = {
resolve: {
modules: [resolve('src'), 'node_modules']
}
}
- externals:提供了「从输出的 bundle 中排除依赖」的方法
```javascript
引入
<script src=”https://code.jquery.com/jquery-3.1.0.js“ integrity=”sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=” crossorigin=”anonymous”配置
const config = { //… externals: { jquery: ‘jQuery’ } }
使用
import $ from ‘jquery’ $(‘.my-element’).animate(/ … /)
- 缩小范围 include / exclude
- include:符合条件的模块进行解析
- exclude:排除符合条件的模块,不解析
- exclude 优先级更高
- noParse
- 不需要解析依赖的第三方大型类库等,可以通过这个字段进行配置,以提高构建速度
- 使用 noParse 进行忽略的模块文件中不会解析 import、require 等语法
```javascript
const path = require('path');
// 路径处理方法
function resolve(dir){
return path.join(__dirname, dir);
}
const config = {
module: {
noParse: /jquery|lodash/,
rules: [
{
test: /\.js$/i,
include: resolve('src'),
exclude: /node_modules/,
use: [
'babel-loader'
]
}
]
}
}
- IgnorePlugin(防止在 import 或 require 调用时,生成以下正则表达式匹配的模块)
- requestRegExp 匹配(test)资源请求路径的正则表达式
- contextRegExp 匹配(test)资源上下文(目录)的正则表达式 ```javascript new webpack.IgnorePlugin({ resourceRegExp, contextRegExp }) $ npm i -S moment
引入 webpack
const webpack = require(‘webpack’)
const config = {
plugins:[
// 配置插件,目的是将插件中的非中文语音排除掉,大大节省打包体积
new webpack.IgnorePlugin({
resourceRegExp: /^.\/locale$/,
contextRegExp: /moment$/
})
]
}
- 多进程配置
- 在小型项目中,开启多进程打包反而会增加时间成本,因为启动进程和进程间通信都会有一定开销
- thread-loader(配置在 [thread-loader](https://link.juejin.cn/?target=https%3A%2F%2Fwebpack.docschina.org%2Floaders%2Fthread-loader%2F%23root) 之后的 loader 都会在一个单独的 worker 池(worker pool)中运行)
```javascript
$ npm i -D thread-loader
const path = require('path');
// 路径处理方法
function resolve(dir){
return path.join(__dirname, dir);
}
const config = {
//...
module: {
noParse: /jquery|lodash/,
rules: [
{
test: /\.js$/i,
include: resolve('src'),
exclude: /node_modules/,
use: [
{
loader: 'thread-loader', // 开启多进程打包
options: {
worker: 3,
}
},
'babel-loader',
]
},
// ...
]
}
}
- 利用缓存(大幅提升重复构建的速度)
- babel-loader 开启缓存
- babel 在转译 js 过程中时间开销比价大,将 babel-loader 的执行结果缓存起来,重新打包的时候,直接读取缓存
- 缓存位置: node_modules/.cache/babel-loader
- cache-loader
- 缓存一些性能开销比较大的 loader 的处理结果
- 缓存位置:node_modules/.cache/cache-loader
- hard-source-webpack-plugin(为模块提供了中间缓存,重复构建时间大约可以减少 80%,但是在 webpack5 中已经内置了模块缓存,不需要再使用此插件)
- dll(在webpack5.x 中已经不建议使用这种方式进行模块缓存,因为其已经内置了更好体验的 cache 方法)
- cache 持久化缓存 ```javascript $ npm i -D babel-loader $ npm i -D cache-loader
- babel-loader 开启缓存
const config = { // 通过配置 cache 缓存生成的 webpack 模块和 chunk,来改善构建速度 cache: { type: ‘filesystem’, }, module: { noParse: /jquery|lodash/, rules: [ { test: /.js$/i, include: resolve(‘src’), exclude: /node_modules/, use: [ // … { loader: ‘babel-loader’, options: { cacheDirectory: true // 启用缓存 } }, ] }, { test: /.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件 use: [ // ‘style-loader’, MiniCssExtractPlugin.loader, ‘cache-loader’, // 获取前面 loader 转换的结果 ‘css-loader’, ‘postcss-loader’, ‘sass-loader’, ] }, // … ] } }
- **优化构建结果**
- 构建结果分析
```javascript
$ npm i -D webpack-bundle-analyzer
# 引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const config = {
// ...
plugins:[
// ...
// 配置插件
new BundleAnalyzerPlugin({
// analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
// generateStatsFile: true, // 是否生成stats.json文件
})
],
};
# 启动命令
"scripts": {
// ...
"analyzer": "cross-env NODE_ENV=prod webpack --progress --mode production"
},
- 压缩CSS
```javascript
$ npm install -D optimize-css-assets-webpack-plugin
压缩css
const OptimizeCssAssetsPlugin = require(‘optimize-css-assets-webpack-plugin’)
const config = { optimization: { minimize: true, minimizer: [ // 添加 css 压缩配置 new OptimizeCssAssetsPlugin({}) ] } }
- 压缩JS
- 在生成环境下打包默认会开启 js 压缩,但是当手动配置 optimization 选项之后,就不再默认对 js 进行压缩,需要手动去配置。
- webpack5 内置了[terser-webpack-plugin](https://link.juejin.cn/?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fterser-webpack-plugin) 插件,无需安装
```javascript
const TerserPlugin = require('terser-webpack-plugin');
const config = {
// ...
optimization: {
minimize: true, // 开启最小化
minimizer: [
// ...
new TerserPlugin({})
]
}
}
- 清除无用的CSS
- purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS ```javascript $ npm i -D purgecss-webpack-plugin // … const PurgecssWebpackPlugin = require(‘purgecss-webpack-plugin’) const glob = require(‘glob’); // 文件匹配模式 // …
function resolve(dir){ return path.join(__dirname, dir); }
const PATHS = { src: resolve(‘src’) }
const config = {
plugins:[ // 配置插件
// …
new PurgecssPlugin({
paths: glob.sync(${PATHS.src}/**/*
, {nodir: true})
})
]
}
- Tree-shaking
- 剔除没有使用的代码,以降低包的体积
- webpack 默认支持,需要在 .bablerc 里面设置 module:false,即可在生产环境下默认开启
```javascript
module.exports = {
presets: [
[
"@babel/preset-env",
{
module: false,
useBuiltIns: "entry",
corejs: "3.9.1",
targets: {
chrome: "58",
ie: "11",
},
},
],
],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
]
}
- Scope Hoisting
- 作用域提升,原理是将多个模块放在同一个作用域下,并重命名防止命名冲突,通过这种方式可以减少函数声明和内存开销。
- webpack 默认支持,在生产环境下默认开启
- 只支持 es6 代码
- 优化运行时体验(运行时优化的核心就是提升首屏的加载速度,主要的方式就是降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载)
- 入口点分割:配置多个打包入口,多页打包
- splitChunks 分包配置
- 代码懒加载
```javascript const ele = document.createElement(‘div’) ele.innerHTML = ‘我是图片描述’ module.exports = eleoptimization: { splitChunks: { chunks: 'all', } }
import logo from ‘../public/avatar.png’
const a = ‘Hello ITEM’ console.log(a)
const img = new Image() img.src = logo
document.getElementById(‘imgBox’).appendChild(img)
// 按需加载 img.addEventListener(‘click’, () => { // 异步加载方式 import(‘./desc’).then(({ default: element }) => { console.log(element) document.body.appendChild(element) }) })
- prefetch 与 preload
- **prefetch** (预获取):浏览器空闲的时候进行资源的拉取
- **preload** (预加载):提前加载后面会用到的关键资源;因为会提前拉取资源,如果不是特殊需要,谨慎使用
```javascript
// 按需加载
img.addEventListener('click', () => {
import( /* webpackPrefetch: true */ './desc').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
import(/* webpackPreload: true */ 'ChartingLibrary');