Webpack入坑指北

初始化项目

首先新建一个项目然后初始化。

1
2
npm init -y
pnpm add webpack webpack-cli -D

创建一个 src/main.js 文件,随便写点啥
在根目录创建 build/webpack.common.js 文件

1
2
3
4
5
6
7
8
9
10
const path = require("path");

module.exports = {
entry: path.resolve(__dirname, "../src/main.js"),
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "../dist"),
clean: true,
},
}

这里需要注意一点是为什么 webpack 配置文件要用 cjs 来写 ?

答案就是因为 webpack 打包过程是在 node 环境下

在 package.json 加上打包命令

1
2
3
"scripts": {
"build": "webpack --config build/webpack.common.js"
}

通过执行 pnpm run build 进行打包,执行后会生成一个 dist 目录,里面就是我们打包的产物。

打包时控制台会报 The 'mode' option has not been set 的警告,意思就是我们需要设置一个 mode 它可以是 development 开发模式、production生产模式、none无模式。

另外如果 shell 命令中参数和配置文件冲突,生效的是 shell 命令参数。

创建 HTML

打包好的产物我们希望能通过html在浏览器中打开看效果,需要通过 html-webpack-plugin 插件实现

1
pnpm add -D html-webpack-plugin

在配置文件中写入

1
2
3
4
5
6
7
8
9
10
const htmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
//...
plugins: [
new htmlWebpackPlugin({
// 设置模板,需要在根目录创建一个 index.html 文件
template: path.resolve(__dirname, "../index.html")
})
]
}

plugins 就是我们声明插件来对 webpack 的功能进行扩展的地方。这时候再打包,发现产物目录下有一个 html 文件,并且通过 script 引入了我们打包的 js 文件。

打包时显示进度

1
2
pnpm add progress-bar-webpack-plugin -D
pnpm add chalk@4.1.2 -D

这里的 chalk 最新版本(5.3.0)使用 esm 引入,我们使用 4.1.2 版本

使用

1
2
3
4
5
6
7
8
9
10
11
const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
module.exports = {
// ...
plugins: [
// ...
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
}),
]
}

再进行打包就能看到进度条了,另外可以通过 chalk 来修改进度条的颜色等。

处理样式

我们定义一个 main.css 文件,在 main.js 引入,进行打包时会发现控制台报错,并提示我们需要用 loader 来处理这个类型的文件。
处理 css 类型文件需要用到以下两个 loader。

1
pnpm add -D style-loader css-loader

在配置文件中声明使用这两个 loader 来处理 css 类型文件。

1
2
3
4
5
6
7
8
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}

module 是我们用来定义某种资源通过指定的 loader 来处理,如上代码我们通过 "style-loader""css-loader" 来处理后缀名为 css 的文件,"css-loader" 做的是解析 css 文件,翻译成 JavaScript 代码,使得 webpack 能够如同处理 JS 代码一样解析 CSS,"style-loader"" 做的是将解析出来的文件通过 style 标签插入到页面中。

另外 loader 的书写顺序也是有要求的,一般情况下,会按照由右到左,由上到下的顺序执行。所以上面代码才是先解析再插入到页面。

通过打包的产物发现我们的 css 已经被打包进 js 文件中,下面我们把 css 代码抽离出来。安装MiniCssExtractPlugin插件

1
pnpm add -D mini-css-extract-plugin

然后进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
// ...
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../index.html"),
}),
new MiniCssExtractPlugin()
]
}

这时候再打包可以看到产物中多一个 css 文件,这样我们就把 css 文件抽离出来了
产物结构
再看产物中的 index.html,多了一行 link 标签,所以这个插件的作用就是把 css 文件抽离并通过 link 标签的形式插入到页面中,这也是为什么在上面的配置中我们去掉了 style-loader。

1
<link href="main.css" rel="stylesheet">

使用 MiniCssExtractPlugin 插件的好处在于,避免了 js 和 css 只能同步加载,对性能有影响,并且其中任意一个更新都会导致缓存失效。另外该插件必须和上面写到的 HTMLWebpackPlugin 插件一起使用(不然连 html 文件都没有怎么插入到页面)。

预处理器

我们以 less 为例

1
pnpm add -D less less-loader

然后新建一个 less 文件,随便写点样式,在打包入口文件导入,接着进行对 less 的配置

1
2
3
4
5
6
7
8
module: {
rules: [
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
}
]
}

无非在处理 css 文件的基础上,多了一个 less-loader ,这个 loader 做的就是把 less 翻译成 css。

postcss

安装 postcsspostcss-loader

1
pnpm add -D postcss postcss-loader

自动添加前缀

postcss 有强大的插件生态来处理各种问题,常用的如 autoprefixer,他能根据配置的浏览器兼容性,检测出需要添加前缀的 css 属性,构建的时候进行添加。

1
pnpm add -D autoprefixer

对 postcss 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
// 添加 autoprefixer 插件
plugins: [require("autoprefixer")],
},
},
},
"less-loader"]
}

在 less 文件中写入

1
2
3
::placeholder {
color: gray;
}

打包后可以看到产物已经增加了前缀

关于系统支持的浏览器可以通过 package.json 的 browserslist 或者 .browserslistrc 文件进行配置
另外postcss-preset-env也可以做同样的事情,甚至更强大,他还能将现代 CSS 转换成大多数浏览器都能理解的东西。

处理 js

webpack 默认是支持处理 js 文件的,但在一些情况下,js 代码会存在兼容性的问题,在低版本的浏览器无法运行的情况,所以我们需要对这部分代码进行处理。这里用到的是 babel 对代码进行转译。

1
pnpm add -D @babel/core @babel/preset-env babel-loader

@babel/core:babel核心库
@babel/preset-env 根据目标环境转译 JavaScript 的预设
babel-loader webpack 中的一个 loader,将 babel 集成到 webpack 构建过程中

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
]
}]
}
}

在入口文件我们声明一个箭头函数并执行,然后分别看使用 babel-loader 进行语法降级和 不使用的效果,可以看到用 babel 后,箭头函数被转译成如下的形式。

1
var fun = function () {/*...*/}

处理 ts

使用 ts-loader

1
pnpm add -D typescript ts-loader

配置

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.ts$/,
use: ["ts-loader"]
},
]
},
}

还需要创建 ts 的配置文件 tsconfig.json

1
2
3
4
5
6
{
"compilerOptions": {
"noImplicitAny": true,
"moduleResolution": "node"
}
}

这时候就可以执行构建了

使用 babel

1
pnpm add -D @babel/preset-typescript

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.ts$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-typescript'],
},
},
],
},
]
},
}

两者区别

使用 @babel/preset-typescript 只是进行了代码转换,而 ts-loader 还会对代码进行类型检查。例如我们将 number 赋值给一个 string 类型。ts-loader 在构建时会报错。
ts-loader构建报错

处理资源

file-loader

安装

1
pnpm add -D file-loader

file-loader 将资源重命名,在代码中插入 url 地址。

url-loader

安装

1
pnpm add -D url-loader

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(png|jpg)$/,
use: [
{
loader: "url-loader",
options: {
limit: 1024
}
}
]
},
]
},
}

url-loader 会对比资源大小和配置的限制大小,如果没超出限制就转为 Base64 编码,超出的话就同 file-loader 一致。

raw-loader

不转译,直接将文件复制到产物。

Webpack5 通过 module.rules.type 指定资源类型。

asset/resource 对标 file-loader
asset 对标 url-loadermodule.rules.parser.dataUrlCondition 用于限定文件大小阈值
asset/source 对标 raw-loader / asset/inline

查看更多

区分开发环境和生产环境

我们上面写到的 webpack.common.js 可以作为一个基础的配置,在开发环境和生产环境中配置也是有差异的,所以我们还需要分别去配置。
创建 build/webpack.dev.jsbuild/webpack.prod.js
因为开发环境和生产环境都基于基础配置,我们需要通过 webpack.merge 将他们连接起来。

1
pnpm add -D webpack-merge

webpack-dev-server

一般开发环境我们希望项目构建后能直接在浏览器运行,这一点通过 webpack-dev-server 来实现

1
pnpm add -D webpack-dev-server

配置

1
2
3
4
5
6
7
8
9
10
11
12
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
const path = require("path");

module.exports = merge(common, {
mode: "development",
devServer: {
hot: true,
open: true,
port: 3000,
},
});

重写启动命令

1
2
3
4
5
{
"scripts": {
"dev": "webpack serve --config build/webpack.dev.js",
}
}

执行 pnpm run dev 将会在 3000 端口启动。

关于打包命名

格式 描述
[name] 文件名称
[hash] 每次webpack构建时生成一个唯一的hash值
[chunkhash] 根据chunk生成hash值,来源于同一个chunk,则hash值就一样
[contenthash] 根据内容生成hash值,文件内容相同hash值就相同

Webpack入坑指北
https://l1ushun.github.io/2023/12/27/webpack-01/
作者
liu shun
发布于
2023年12月27日