webpack 最佳实践
共 31545字,需浏览 64分钟
· 2021-07-12
前言
本文讲述的最佳实践是从日常业务中总结而出的,不一定适合所有项目。毕竟每个公司或个人的项目不同,最佳实践也会有所不同。但是可以从这篇文章借鉴吸收一点有用的东西,有问题的地方也欢迎大家积极吐槽指正
。
为了避免出现 我这明明可以,你那怎么不行
的尴尬情况,这里列一下文章涉及到依赖的版本号。
├── webpack 5.39.1
├── webpack-cli 4.7.2
├── node 12.8.0
├── npm 6.10.2
正文
初始化项目
1. mkdir test-app && cd test-app
2. npm init
首先添加一个入口文件 /src/index.js
和 webpack 配置文件 webpack.config.js
,现在我们的目录结构如下
test-app
├── src
| └── index.js
├── package.json
├── webpack.config.js
安装 webpack
npm install webpack webpack-cli -D
开始搞事情
在 src/index.js
中随便写点东西
class Test {
constructor() {
document.writeTest('hello world')
}
}
new Test()
先来打个包看看啥效果, 执行命令 npx webpack
![](https://filescdn.proginn.com/814f72da44223638245c3ca0c5fe413a/b65fcbe19b1b5d6ce74c05adfa7f1d4d.webp)
等待一段时间后,看到目录有了变化, 新增了一个 dist
目录,该目录下有一个 main.js
文件
test-app
+ ├── dist
+ | └── main.js
├── src
| └── index.js
├── package.json
├── webpack.config.js
让我们来看看 main.js
里有点啥
new class{constructor(){document.writeTest("hello world")}};
这玩意都不用试,肯定不得行啊,得将 js 代码转成 es5 才行。首先安装下babel-loader
及几个相关的依赖
配置 babel
babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-proposal-private-methods @babel/runtime @babel/runtime-corejs3
npm install babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-proposal-private-methods -D
npm install @babel/runtime @babel/runtime-corejs3 -s
修改 webpack.config.js
文件, 添加 babel-loader
配置
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash:8].js',
},
module: {
rules: [
{
test: /\.(jsx|js)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
]
}
}
根目录下添加相应的 .babelrc
配置文件
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-transform-runtime", {"corejs": 3}],
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
["@babel/plugin-proposal-private-methods", { "loose": true }]
]
}
再次执行命令 npx webpack
来打个包。完成后查看目录结构
test-app
├── dist
+ | ├── bundle.b8ba1739.js
| ├── main.js
├── src
| └── index.js
+ ├── .babelrc
├── package.json
├── webpack.config.js
查看构建后的 bundle.b8ba1739.js
文件
(()=>{"use strict";new function n(){!function(n,t){if(!(n instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n),document.writeTest("hello world")}})();
构建产物看着没什么问题了,接下来看下在浏览器中的实际效果。要看效果,肯定离不开 html
文件。
浏览器中观看效果
作为一个伸手党直接从社区嫖来一个插件 html-webpack-plugin
,这个插件的作用是将打包产物引入到我们提前准备好的模板 .html
文件中,我们访问这个文件就能直观的看到效果了
先来安装下插件
npm install html-webpack-plugin -D
接着创建一个 public
目录, 用来存放静态资源。新增一个 index.html
模板,放在 public 目录下
test-app
├── dist
| ├── bundle.b8ba1739.js
| ├── main.js
├── src
| └── index.js
+ ├── public
+ | └── index.html
├── .babelrc
├── package.json
├── webpack.config.js
在 webpack.config.js
中配置 html-webpack-plugin
// 省略 ...
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 省略 ...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
inject: 'body',
scriptLoading: 'blocking',
}),
]
}
再次执行命令 npx webpack
来打个包。打完包发现 dist
目录下多了一个 index.html
文件。浏览器中打开 index.html
看看对不对
![](https://filescdn.proginn.com/186a9c44a23a682501cf7b8793dd644c/13416f968352f9344b2b48bab9dda2e1.webp)
作为一个 api 工程师,连 api 都能记错。
![](https://filescdn.proginn.com/ada9479e39850d37e9546e9959386e6d/3b8e9e89241ecd6f9ad11856f0544b35.webp)
修改下 src/index.js
代码
class Test {
constructor() {
document.write('hello world')
}
}
new Test()
再次执行命令 npx webpack
来打个包。老步骤,先检查下打包产物对不对
test-app
├── dist
| ├── bundle.b8ba1739.js
+ | ├── bundle.dc044571.js
| ├── index.html
| ├── main.js
├── src
| └── index.js
├── public
| └── index.html
├── .babelrc
├── package.json
├── webpack.config.js
看样子应该没错,代码修改了,打包后多了个 .js
文件。再看看效果对不对
![](https://filescdn.proginn.com/ffe00505b974f3e0a1ed08c51c21be83/6bd08dd4eaf79bbde595ba750179e35e.webp)
界面上也出现了 hello world
。到这里为止,算是利用 webpack
走通了一个最小流程。
为什么说是最小,因为到目前为止这个配置在实际工作中 基本没卵用 实用性不大
。细心一点的人已经看出来了,上面存在三个问题
每修改一次代码,都要走一遍打包流程,然后自己手动打开 html 文件,预览效果 第一次调用错误 api 的时候,报错信息定位不精确 打包目录下面 上次构建产物
也仍旧存在,时间长了会存在越来越多的无用代码
作为一个懒人,第三点可以忍,第一点和第二点忍不了。我们一个个来解决
实时更新并预览效果
针对第一点,查阅 webpack
官网,发现官网就给指了一条明路
![](https://filescdn.proginn.com/27ab2b566aa43a9df93be4f4d551b3df/fe0d7135b42f25cb1787c4f8b2da782e.webp)
按照官网教程,首先安装下 webpack-dev-server
npm install webpack-dev-server -D
再在 webpack.config.js
中添加相应的配置
// 省略 ...
module.exports = {
// 省略 ...
devServer: {
port: '3001', // 默认是 8080
hot: true,
stats: 'errors-only', // 终端仅打印 error
compress: true, // 是否启用 gzip 压缩
proxy: {
'/api': {
target: 'http://0.0.0.0:80',
pathRewrite: {
'/api': '',
},
},
},
},
}
在 package.json
> script
中添加一个命令
"dev": "webpack serve --open",
执行 npm run dev
,这个时候在动在浏览器中打开了 http://localhost:3001/
页面。光自动打开还不够啊,我们的目标是每次修改后不用构建
就能在浏览器中实时查看
。为了测试这个功能是否生效,我们任意修改 src/index.js
文件并保存。发现浏览器中内容自动刷新生效了。
想了解更多关于 devServer
的可以阅读以下两篇文章
【Webpack】devServer 实验报告 120 行代码帮你了解 Webpack 下的 HMR 机制
sourcemap 配置
第一个问题好了,再来看看第二个问题 报错信息定位不精确
。我们仍旧在官网找找看,有没有对应的解决方案。通过 1 小时的文档阅读和 7 小时的摸鱼,终于在一天后找到了解决方法。
我们在 webpack.config.js
中添加配置
// 省略 ...
module.exports = {
// 省略 ...
devtool: 'eval-cheap-module-source-map',
}
这个配置什么意思呢,它会告诉我们错误是在原始代码
的哪一行发生的。废话不多说,先来看看效果
![](https://filescdn.proginn.com/46254dd02c764ee9a9d26a609a282bcd/6ad057804305a17dd701dbb6ae19368f.webp)
点进去看看是什么情况
![](https://filescdn.proginn.com/d90ca6778fd584d3af031937f377a610/a4b80956da5779dfb245595d216741d6.webp)
![](https://filescdn.proginn.com/a175e8bc983d31ec9980f7751e5a9d63/9555934454e503e41488e206ab1ed31a.webp)
这么精准的定位,一天可以改100
个 bug 了。
但是!!!这玩意好归好,生产环境可不能乱用。这里建议
开发环境
最佳:eval-cheap-module-source-map生产环境
最佳:hidden-source-map
什么?你上下嘴皮子吧嗒一合,说最佳就最佳?没有拿得出手的理由我们是不会信的
![](https://filescdn.proginn.com/e12af043f2100c515afea09487b1b79e/b3d8c4affdce91f3268b540414ea453c.webp)
造谣是不可能造谣的,这辈子都不会。我也是吸收了这篇文章 万字长文:关于 sourcemap,这篇文章就够了 的精华才总结出来的。
一万字的文章总结成两句话,10 秒钟吸收
![](https://filescdn.proginn.com/39432847250c16edd4f2728f89b716fa/44468f9bc8d9fdd5d6914ed3c0db0f88.webp)
世事总是这么奇妙,按照上面的思路,在解决第二个问题的时候又带出了一个新的问题,某些配置可能需要区分环境来设置,不同的环境设置合适的配置。就像在解决一个bug A
的过程中,发现了一个新的 bug B
。看来在解决上面第三个问题之前,得先解决这个 区分环境配置
的问题了。
拆分环境
按照一般惯例,我们会有 开发
、测试
、预发
、生产
几个环境。但是我个人很多情况下 开发
和 测试
环境是同一套配置,所以我这里直接省略 测试
这个环境。
修改下目录结构
test-app
+ ├── build
+ | ├── webpack.base.js
+ | ├── webpack.dev.js
+ | ├── webpack.pre.js
+ | ├── webpack.pro.js
├── dist
├── ├── bundle.b8ba1739.js
├── ├── bundle.dc044571.js
| ├── index.html
| ├── main.js
├── src
| └── index.js
├── public
| └── index.html
├── .babelrc
├── package.json
从目录中就可以看出一点东西,我们删除了原先根目录下的 webpack.config.js
文件。新增了一个 build
目录。在 build
目录下我们需要建一个 webpack.base.js
文件。用来存放各个环境公共的配置,毕竟不可能所有配置在各个环境中都不一样。然后按照我们各自项目实际的需求来建立不同环境的配置文件。
先修改公共配置文件 webpack.base.js
。原先的 devServe
配置由于只有开发环境有;devtool
各个环境不一样,所以这两个配置从公共配置里移除了
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const rootDir = process.cwd();
module.exports = {
entry: path.resolve(rootDir, 'src/index.js'),
output: {
path: path.resolve(rootDir, 'dist'),
filename: 'bundle.[contenthash:8].js',
},
module: {
rules: [
{
test: /\.(jsx|js)$/,
use: 'babel-loader',
include: path.resolve(rootDir, 'src'),
exclude: /node_modules/,
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(rootDir, 'public/index.html'),
inject: 'body',
scriptLoading: 'blocking',
}),
],
}
接下来配置各个环境的配置,这里主要用到一个 webpack-merge
插件,用来合并公共配置,执行 npm install webpack-merge -D
修改 webpack.dev.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base');
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
port: '3001', // 默认是 8080
hot: true,
stats: 'errors-only', // 终端仅打印 error
compress: true, // 是否启用 gzip 压缩
proxy: {
'/api': {
target: 'http://0.0.0.0:80',
pathRewrite: {
'/api': '',
},
},
},
},
});
因为这里不涉及到实际的项目开发,所以这里预发
和生产
两个环境的文件先配置成一样的,大家可以根据自己的实际需要来进行不同的配置。
webpack.pre.js
webpack.pro.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base');
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'hidden-source-map',
});
看到仔细的人已经发现,配置中多了一个 mode
属性,这个会在后面解释一波,这里先不讲
修改 package.json 中的命令
"scripts": {
"dev": "webpack serve --config build/webpack.dev.js --open",
"build:pro": "npx webpack --config build/webpack.pro.js",
},
再次执行 npm run dev
看看效果
![](https://filescdn.proginn.com/7da751fe13f43ea03c2bf76a2de62582/cd50f010ab4faacacbf470d484afac7c.webp)
看来是没问题了,现在已经成功把 webpack.config.js
文件根据环境进行拆分成了多个文件。
现在来回顾下之前提出的第三个问题
![](https://filescdn.proginn.com/18e582981ab50fe98fedf1406d98b4b7/14110ef2bde5d9633cf8dc6ff8a2d7a7.webp)
这个项目小的时候其实问题不大,但是当项目大了之后,每次打包都增加几百上千的文件,还是有点恐怖的。所以还是把这个问题也顺带解决下好了。
打包时清除上次构建产物
我们的目标是每次打包时删除上次打包的产物,保证打包目录下所有文件都是新的,社区查找一番后,找到一个插件clean-webpack-plugin
,来看下这个插件的介绍
![](https://filescdn.proginn.com/90287b97b1caa0b7f4fd1ca697ea8596/4fbbe22e587f065990b0e46626b7f049.webp)
比较懒,所以直接上截图了。老步骤,先安装 npm install clean-webpack-plugin -D
然后直接将文档中的示例代码借鉴
到我们的项目中。修改 webpack.base.js
// 省略...
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// 省略...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(rootDir, 'public/index.html'),
inject: 'body',
scriptLoading: 'blocking',
}),
new CleanWebpackPlugin(),
],
}
试下效果,执行 npm run build:pro
打个包。查看目录
test-app
├── build
| ├── webpack.base.js
| ├── webpack.dev.js
| ├── webpack.pre.js
| ├── webpack.pro.js
├── dist
| ├── bundle.fd44c2eb.js
| ├── bundle.fd44c2eb.js.map
| ├── index.html
├── src
| └── index.js
├── public
| └── index.html
├── .babelrc
├── package.json
dist
目录下原先存在的 main.js
、bundle.b8ba1739.js
等前几次打包产物已经自动清除了。到这里第三个问题也解决了
功能完善
添加 css 和 less 支持
为什么不添加 sass
支持?因为我不用 sass
首先,在 src
目录下添加一个 index.less
文件
.test {
color: red;
}
修改 src/index.js
文件,在文件中引用刚才添加的 less
文件
import './index.less'
class Test {
constructor() {
this.renderDiv()
}
renderDiv() {
const div = document.createElement('div')
div.className = 'test'
div.innerHTML = 'hello world'
document.body.appendChild(div)
}
}
new Test()
执行 npm run dev
等待 10 分钟后,页面迟迟没有加载任何东西,打开控制台一看
![](https://filescdn.proginn.com/dd8b6a8efd834402fbfc3f7d5c5b2eec/a68c6a78d3e22b75ef0c819be155585b.webp)
英语 8 级的我立马读懂了报错:"你 可能 需要 一个 什么什么 loader 来 处理 这个 文件 类型, 目前 没有 loaders 被配置 来 process 这个 文件"
再结合官网的说明
![](https://filescdn.proginn.com/73507719372a02f0e88157fcceccc757/91c4ca94c2894b648fe52595d1cd1b94.webp)
到了这里,我好像隐约明白了 webpack
的真谛:虽然很多时候我不行,但是很多大佬会让我行
。呸,什么叫不行?这叫 灵活可插拔
,正是这种特性,让 webpack 可灵活支持各种复杂场景的自定义配置。
忘了正事儿,既然问题找到了,就好解决了,找到几个处理 css
和 less
的 loader 就行
首先安装 loader
npm install less style-loader css-loader less-loader -D
再修改 webpack.base.js
文件
// 省略...
module.exports = {
// 省略...
module: {
rules: [
// 省略...
{
test: /\.(le|c)ss$/,
exclude: /node_modules/,
use: ['style-loader', 'css-loader', 'less-loader']
},
]
},
// 省略...
}
再次执行 npm run dev
,查看效果
![](https://filescdn.proginn.com/2bed442331e11cc5b5a4039489f1752e/d51eb1849e04fa18f867b59289f897ae.webp)
css module
这一块是基于上面的模块修改的,解决 css
命名混乱和冲突的。不需要的话可以直接跳过这一块。
修改 webpack.base.js
// 省略...
module.exports = {
// 省略...
module: {
rules: [
// 省略...
{
test: /\.(le|c)ss$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
compileType: 'module',
localIdentName: "[local]__[hash:base64:5]",
},
},
},
'less-loader'
]
},
]
},
// 省略...
}
执行 npm run dev
来看看效果
![](https://filescdn.proginn.com/68b13a799e15ab5898d4b117c6aa6a0b/1b83bc33b862ebe4af62c92ae71bf50d.webp)
给 class
样式名称后加上一个哈希串,具体的配置可以看 css-loader 官网
css 自动添加前缀
首先安装插件
npm install autoprefixer postcss postcss-loader -D
修改 webpack.base.js
配置文件
// 省略...
const autoprefixer = require('autoprefixer');
module.exports = {
// 省略...
module: {
rules: [
// 省略...
{
test: /\.(le|c)ss$/,
exclude: /node_modules/,
use: [
// 省略...
'less-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
["autoprefixer"],
],
},
},
}
]
},
]
},
// 省略...
}
打包后抽离 css 文件
首先安装 mini-css-extract-plugin
插件
npm install mini-css-extract-plugin -D
修改 webpack.base.js
配置文件
// 省略...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// 省略...
module: {
rules: [
// 省略...
{
test: /\.(le|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
// 省略...
]
},
]
},
plugins: [
// 省略...
new MiniCssExtractPlugin({
filename: 'css/[name].css',
}),
],
}
执行 npm run build:pro
打个包看看效果。
![](https://filescdn.proginn.com/5778e976d44aa0b056a4870ce148c95d/4116a8dd33b7e3a543f79148f2b82eb9.webp)
可以看到 css 已经被抽离出来了
压缩打包后的 css 文件
首先安装 optimize-css-assets-webpack-plugin
插件
npm install optimize-css-assets-webpack-plugin -D
修改 webpack.base.js
配置文件
// 省略...
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
// 省略...
plugins: [
// 省略...
new MiniCssExtractPlugin({
filename: 'css/[name].css',
}),
new OptimizeCssPlugin(),
],
}
执行 npm run build:pro
打个包看看效果。
.test__1PSRs{color:red;transition-duration:.4s}
可以看出 css 已经被压缩了
复制静态资源到打包目录
有些时候有些第三方的 js 插件没有提供 npm 包,只提供了一个 cdn 地址或者一份文件需要自己下载下来。通常我们下载下来之后放在我们的 public/js
目录下面,然后 public/index.html
文件里直接用 script
标签引入。这个时候不管是 npm run dev
开发时,还是 npm run build:pro
构建后,这个 js 文件都是找不到的。我们可以尝试下
在 public/js
新加一个 test.js
的空文件,啥内容都不用。然后在 public/index.html
中引入这个文件
<!DOCTYPE html>
<html lang="en">
<head>
// 省略
</head>
<body>
<div id="root"></div>
<script src="./js/test.js"></script>
</body>
</html>
执行 npm run dev
查看效果
![](https://filescdn.proginn.com/a17f9a701647a508f3fdefeac4578902/2d8ba6ab36a65b7ae4562f00cc51d968.webp)
这里我们可以用 copy-webpack-plugin
这个插件,在构建的时候,将 public/js
的静态资源复制到 dist
目录下,这样文件就能找到了
安装插件 npm install copy-webpack-plugin -D
修改 webpack.base.js
配置文件
// 省略...
const CopyWebpackPlugin = require('copy-webpack-plugin');
const rootDir = process.cwd();
module.exports = {
// 省略...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(rootDir, 'public/index.html'),
inject: 'body',
scriptLoading: 'blocking',
}),
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: '*.js',
context: path.resolve(rootDir, "public/js"),
to: path.resolve(rootDir, 'dist/js'),
},
],
})
new MiniCssExtractPlugin({
filename: 'css/[name].css',
}),
new OptimizeCssPlugin(),
],
}
执行 npm run dev
查看效果
![](https://filescdn.proginn.com/8eb9448e1ae2adafd187402f6409f435/5c33e20f09522b6b652200c09999a1fc.webp)
静态文件已经可以正常加载了。
资源加载器
项目中难免要引入一些图标、图片等资源,在不做任何处理的情况下,我们尝试下在代码中引用图片,修改 src/index.js
文件如下
import wuhanjiayou from '../public/asset/a.jpeg'
class Test {
constructor() {
this.renderImg()
}
renderImg() {
const img = document.createElement('img')
img.src = wuhanjiayou
document.body.appendChild(img)
}
}
new Test()
执行 npm run dev
看下效果,报了个熟悉的错
![](https://filescdn.proginn.com/821f06dfdcad3462997fc9582ac927dc/cd0d85bf83df83fefe7c0c498a757f19.webp)
按照以往的套路,直接引用社区的三件套 raw-loader
、url-loader
、file-loader
,安装依赖,配置依赖,一通操作下来就解决了问题。现在我们使用 webpack5
就方便多了,不用安装任何依赖,直接修改 webpack.base.js
配置文件
// 省略...
rules: [
{
test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
type: 'asset',
},
]
没错,就是这么简单。type
属性还有其他几个值,具体可以看 官方文档
配置已经修改好了,执行 npm run dev
再来看下效果
![](https://filescdn.proginn.com/36013be58f2a41762020785621f2e3b2/dd32df81ecf1b120f708306e359bcf8a.webp)
搞定!
上面讲到的东西基本够小项目的日常开发需求了,常用的 loader 和 plugin 都已经有所涉及。但是,如果你的项目特别复杂,需求又比较小众,社区没有现成的 loader 和 plugin 可以借鉴,那么只能自己动手实现
一个了。
![](https://filescdn.proginn.com/8e4672b752136734f25629cfe4801172/f8733c2424849d2777ae22db2ec1d2bd.webp)
可能在一部分人眼中,loader 和 plugin 是比较神秘的,也不可能想着自己去造一个轮子。但是当碰到问题又没有现成的解决方案的时候,那就不得不自己造了。
看了这篇文章 Webpack - 手把手教你写一个 loader / plugin 应该能很快上手
项目优化
刚才也讲到了,上面的一通操作基本够小项目开发使用了。为什么是小项目?大项目不行吗?当一个项目大到路由都有几百个
的时候,一次热更新就需要十几二十多秒,一次打包要半个小时。开发时,一次代码改动保存就要等 20 秒
,这搁谁谁都忍不了啊。这个时候就需要想点办法来优化。
loader 配置优化
这个其实上面已经做了。明确告诉 loader,哪些文件不用做处理(exclude),或者只处理哪些文件(include)。
{
test: /\.(jsx|js)$/,
use: 'babel-loader',
// include: [path.resolve(rootDir, 'src')]
// exclude: /node_modules/,
},
一般倾向于使用 include
,但是如果怕漏处理一些文件的话,粗暴点,使用 exclude: /node_modules/
也可以。
这部分测试了下,提升速度不是很明显,应该算锦上添花吧
缓存
先说下 webpack5
之前是怎么做的。
利用 cache-loader
将结果缓存中磁盘中;利用 hard-source-webpack-plugin
将结果缓存在 node_modules/.cache
下提升二次打包速度;利用 DllReferencePlugin
将变化不频繁的第三方库提前单独
打包成动态链接库,提升真正业务代码的打包速度
webpack5 自带了持久化缓存,配置如下
开发环境 webpack.dev.js
cache: {
type: 'memory'
},
生产环境 webpack.pro.js
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
这个测试了下前后的打包时间
![](https://filescdn.proginn.com/cb690bb43f5949414ec66b09776c23ed/df12f78a9df5afc25fc8b014e277b4d8.webp)
![](https://filescdn.proginn.com/11a4e468a97f754c6df0fc9322342e83/1ce93b40900387d3f02735894771bf2f.webp)
数据是这个数据:
第一次: 12592 ms
第二次: 920 ms
但是我心里默数了下,二次打包大概在 3 秒左右,咱也不知道控制台的计时逻辑是什么
![](https://filescdn.proginn.com/81b563f6e6aa6e33c147837b42005c48/35520d363bee331d1826d3e384afd9d2.webp)
如果在构建时,你主动确定要放弃旧的缓存的话,可以传一个新的 version
参数来放弃使用缓存
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
},
version: 'new_version'
},
代码拆分
optimization: {
splitChunks: {
chunks: 'all',
}
}
这个在 mode: production
时是默认开启的,但是默认情况下只会对按需加载的代码进行分割。如果我们要对一开始就加载的代码也做分割处理,就要进行如上配置。
从官网截了一张图:
![](https://filescdn.proginn.com/6ed922025a47d24d403385923f5f7cff/c908785271567bcb172628f481e034c7.webp)
大家的项目可能都有所不同,相对应的最佳的配置可能也有所不同,所以这里就补贴具体的配置了,大家有需要的可以看官网的文档对自己的项目进行配置 官网 optimization.splitChunks 更多配置、「Webpack」从 0 到 1 学会 code splitting
mode
mode: production
在上面出现了这么多次,也没有具体说有哪些功能。其实当设置 mode: production
时,webpack
已经默认开启了一些优化措施。
![](https://filescdn.proginn.com/911f6bcbae4c440c8b674028becab1ea/b717a3caba3b2da5dcc1182b97c27264.webp)
这里面的一些东西由于篇幅较大也不做一一说明了,反正只要记得 mode: production
已经给我们做了一系列优化,真的想知道有哪些优化的,我找了篇文章,有兴趣的可以看看 mode 详解
happypack
利用 happypack
插件进行多线程打包,按照官网文档进行配置
// 省略...
const Happypack = require('happypack');
const os = require('os')
const happyThreadPool = Happypack.ThreadPool({ size: os.cpus().length })
// 省略...
rules: [
{
test: /\.(jsx|js)$/,
// use: 'babel-loader',
use: 'Happypack/loader?id=js',
exclude: /node_modules/,
},
]
plugins: [
new Happypack({
id: 'js', // 这个 id 值为上面 Happypack/loader?id=js 问号后跟的参数
use: ['babel-loader'],
threadPool: happyThreadPool
}),
]
由于本篇文章写的是个 demo,没有业务代码,所以这个打包出来的时间基本没变化甚至还多了 1 秒
,这里就不贴效果图了。这是因为happypack
执行也需要时间,如果项目比较小的话,就不需要配置了。js
处理完之后那就是要处理css
了, 按照处理js
的方式,ctrl+c/ctrl+v
处理css
。
执行 npm run build:pro
ERROR in ./src/index.less
Module build failed (from ./node_modules/Happypack/loader.js):
Error: You forgot to add 'mini-css-extract-plugin' plugin (i.e. `{ plugins: [new MiniCssExtractPlugin()] }`), please read https://github.com/webpack-contrib/mini-css-extract-plugin#getting-started
报错说忘记添加了mini-css-extract-plugin
插件,但是明明已经添加了,经过试验,发现是 mini-css-extract-plugin
这个插件引起 happypack
报错的。终于,在经过 百度
、谷歌
等一系列骚操作后,我放弃了,没找到解决方法
![](https://filescdn.proginn.com/aa976e76b6c128c30270c595e0fcc3a5/bb134854e9230aa529f1770385784fbf.webp)
现在摆在面前的就三条路:
放弃使用 happypack
使用 happypack
优化js
和css
,放弃使用mini-css-extract-plugin
使用 happypack
优化js
,放弃优化css
,保留mini-css-extract-plugin
知道咋解决的或有更好的方式的可以在下方留言,让我白嫖 借鉴下。
thread-loader
如果采用上面第一种,放弃使用 happypack
,可以用 thread-loader
代替下。而且这个配置非常简单。
先安装: npm install thread-loader -D
,再修改配置
// 省略...
rules: [
{
test: /\.(jsx|js)$/,
use: ['thread-loader', 'babel-loader'],
exclude: /node_modules/,
},
{
test: /\.(le|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'thread-loader',
{
loader: 'css-loader',
options: {
modules: {
compileType: 'module',
localIdentName: "[local]__[hash:base64:5]",
},
},
},
'less-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
["autoprefixer"],
],
},
},
}
],
},
]
到这里应该可以应付一般的项目了。由于这篇文章主要讲述 webpack 的应用,所以很多知识点没有细讲,也没有精力细讲,但是很多涉及到的知识点都推荐了相应的文章,有兴趣的朋友可以看一下。
参考文献:
webpack5 webpack4 升级到 5
请你喝杯🍵 记得三连哦~
1.阅读完记得给🌲 酱点个赞哦,有👍 有动力
2.关注公众号前端那些趣事,陪你聊聊前端的趣事
3.文章收录在Github frontendThings 感谢Star✨