webpack打包工具基本使用

什么是webpack

我们在进行react、vue2开发的时候,经常会提到工程化,打包工具和webpack啥的,还有项目完成后还要进行打包部署操作啥的。

那么,我就有点疑惑,为啥要打包后进行部署?webpack又是啥?

首先,我们先看一下webpack的官方解释。

At its core, webpack is a static module bundler for modern JavaScript applications.

从本质上讲,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包器 。

所以,webpack就是个打包工具,那么为什么要进行打包,打包这一步骤有什么好处?

首先,我们会发现,webpack经常会出现在工程化项目中,而工程化又是什么?

前面提到过:

工程化:工程化是指将系统化、标准化的方法应用于项目或产品的设计、开发、测试、部署、维护各个环节,强调通过科学的方法和工具来提高效率、保证质量、降低风险。

简单来说:工程化就是运用模块化和组件化开发,有代码规范和质量检测功能,具有自动化流程能力,不必过多考虑配置如何实现,只需要专注于业务逻辑的一种开发方式。

其实webpack就可以帮助我们实现上面的好处,如果是通过脚手架开发的话,一些配置已经默认帮我们配置好了,我们只需要关注业务代码,最后打包后进行部署即可。

又提到打包了,打包是什么呢?

在我看来,打包其实就是工程化开发和原始前端开发的桥梁,我们的工程化项目是不能被浏览器直接所使用的,我们需要将其转化为原始的html、css、js,打包就是代码转化的过程,但在这个过程中,我们可以做一些额外操作,对代码的质量进行提升。比如代码压缩、转化、剪枝等操作,保证兼容性,减少代码体积,提高传输效率等优化。这些操作,对于原始前端开发,都是很困难的,我们每次都需要手动处理、优化、检测、压缩等操作,导致我们在这些步骤上可能会浪费太多时间,而减少了对业务代码的专注,这样开发效率其实是很差的。

官方图片

webpack使用

webpack的使用依赖node环境,我们首先要保证node环境已经安装好。

webpack的使用需要依赖两个包:webpackwebpack-cli

webpack-cli主要是为了让我们在命令行能使用webpack命令进行打包操作,也是为了配置在package.json中作为脚本命令。

但我们也可以直接使用webpack包,有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第一种,传递回调函数,查看错误信息或构建成功后的状态信息

webpack(
{
entry: './src/test.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash:5].js',
},
mode: 'production',
},
(err, stats) => {
if (err) {
console.error('致命错误:', err.stack || err);
return;
}
if (stats.hasErrors()) {
console.error('构建错误:', stats.toString({ colors: true }));
return;
}
console.log('构建成功,文件列表:', Object.keys(stats.compilation.assets));
}
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 第二种:使用run函数,在run中传递回调函数,查看错误信息或构建成功后的状态信息

webpack({
entry: './src/test.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash:5].js',
},
mode: 'production',
}).run((err, stats) => {
if (err) {
console.error('致命错误:', err.stack || err);
return;
}
if (stats.hasErrors()) {
console.error('构建错误:', stats.toString({ colors: true }));
return;
}
console.log('构建成功,文件列表:', Object.keys(stats.compilation.assets));
});

这样操作起来属实有点麻烦,我还是喜欢通过命令行操作,但前提是要下载webpack-cli包。

然后直接在命令行输入命令npx webpack ./src/test.js回车即可。

我们可以在package.json中添加scripts命令,方便我们使用。

1
2
3
"scripts": {
"build": "webpack ./src/test.js"
}

注意:配置在package.json中的命令,不需要使用npx,它会自动查找node_modules中.bin中的webpack可执行命令。

前面直接使用webpack包的操作,我们会发现传入了一个配置对象,我们可以通过配置特定信息来改变打包后的产物,如果我们通过使用webpack包的方式,配置起来有些麻烦,但如果我们通过命令行操作的方式,我们只需创建一个配置文件,到时候webpack会自动读取该配置文件进行一些列操作,这个文件名叫webpack.config.js,当然我们也可以通过某种方式改变默认配置文件的指向。

这里展示一个简单的配置文件

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

module.exports = {
mode: 'production', // 模式(生产模式production|开发模式development)
entry: './src/test.js', // 入口文件
// 输出文件
output: {
path: path.resolve(__dirname, './dist'), // 输出路径
filename: 'bundle.[contenthash:4].js', // 输出文件名
},
};

其实webpack.config.js中的配置都是可以通过命令行传入的,而且通过命令行传入参数的优先级比配置文件的高。

比如我们想通过命令行指定输出文件的路径,可以这样写:npx webpack --output-path='./bundle',当然也可以配置在package.json的scripts中。

如果我们不想用这个默认的配置文件,比如我想用wk.config.js,我们只需要在命令行通过config进行指定即可。

1
2
3
webpack --config=wk.config.js

webpack --config wk.config.js

这么就会根据wk.config.js中的配置进行打包了。

以上我们只说了webpack的打包入口文件、输出文件等这些基本配置,对于打包输出的文件,比如我需要打包的test,js文件内容为const test = 'hello world';console.log(test),生产模式下打包后的文件内容也就是console.log('hello world'),看起来根本没有什么区别,而且打包的也就是js文件,说明webpack默认是支持打包js文件的,但它是否能对代码进行兼容性处理?如果文件时css、html甚至是图片,它又是否支持?如果支持,又是怎么操作的呢?

这就引出了我接下来要说的webpack中的loaderplugin了。

处理css的loader

前面我们提到了,webpack是工程化代码和原始前端开发的桥梁,它可以将工程化代码转化为原始前端开发形式代码,同时它还能进行一些优化操作。

而前面webpack官网的解释又提到:webpack 是一个用于现代 JavaScript 应用程序的静态模块打包器。 而且我们发现在配置webpack时,入口文件一般都是js文件,特别是vue和react的脚手架已经默认指定了入口文件为main.js或index.js。

综上所述,我们可以提取出一些信息:

  • webpack是用于打包js的工具。

  • webpack支持模块化。

  • webpack是现代化开发的工具。

关于现代化开发技术,牵涉到很多,但提到css我们就会想到css预处理器,比如less、sass、stylus等,以及它们的转化和优化工具postcss。

而且我们可以测试一下,如果入口文件为css文件,最终打包结果还是js形式。

所以我们可以总结一下,关于webpack,是支持模块化的,并且可以通过模块化引入css等文件,并对其处理为js数据,最终通过某种方式将该js数据转化为真实的css插入到页面中。

而将css文件转化为js数据,就是loader做的事了,进而可以得出,关于除了js文件外的文件类型,都需要通过loader转化为可被js模块系统识别的形式(如路径、字符串、js对象等)。

这里介绍几个常见的loader。

css-loader

前面我们提到了,wepack默认是支持打包js文件的,但对于css文件,需要使用loader将其转化为可被js模块系统识别的形式,而处理css文件的loader工具,我们可以选择使用css-loader

1
npm i css-loader -D

css-loader的使用

前面我们已经安装了css-loader,目前对于它的使用有两种方式:

  1. 内联方式。

    • import 'css-loader![文件路径]'
    • 如:import 'css-loader!../css/index.css'
  2. 配置方式:在配置文件中进行配置。

上图我们可以发现,css-loader是配置在module配置项中,我们可以知道,module中是用于配置loader的,我们看一下module中的其他配置项:

  • rules:用于配置loader的匹配规则,其值是一个数组,可以填写多个loader规则。

  • test:用于对资源进行匹配,通常会设置成正则表达式。

  • use:其值可以是一个数组,存放UseEntry对象,也是以是一个UseEntry对象,也可以只是一个字符串。

    • 值是数组时:可以配置多个UseEntry对象或字符串共同对资源处理。
    • 值是一个UseEntry对象时,表示只使用一个loader处理匹配的资源。
    • 值是一个字符串时,表示使用一个loader处理匹配的资源,并且该loader的使用不需要额外配置。
  • UseEntry:描述loader的配置对象,有以下配置:

    • loader:loader的名称,可以是一个字符串,也可以是一个函数。
    • options(可选):loader的配置项,值会被传到laoder中。
    • query:目前已被options替代。

style-loader

经过css-loader的配置后,webpack已经能够正确处理css文件了,但这个时候我们并不能使用css内容,因为这个一步仅仅是将css文件转化为可被js模块系统识别的形式,他并没有被插入到页面中。

这个时候我们就需要使用style-loader了,这个loader可以帮助我们将css内容插入到style标签中,然后插入到页面中。

使用步骤:

(1) 安装style-loader

1
npm i -D style-loader

(2) 配置style-loader

webpack中loader的执行顺序是从右到左、从下到上,所以我们需要将style-loader写到css-loader前面,同时由于我们需要css文件进行多个loader处理,所以use项需要是一个数组。

(3) 引入打包后的文件,我们可以发现css样式生效,并在head中插入了style标签。

处理预处理器样式(以Less为例)

在项目开发中,我们通常会选择使用lesssass等css预处理器提高开发效率,但我们前面说的都是针对于css资源的处理,所以我们想使用less这些预处理器,只需要保证能将less转为css即可。

我们有两种方式可以实现lesscss的转换:

(1) 通过less命令将less文件转为css文件

1
2
3
4
5
6
# 安装less
npm install -D less

# 将less文件转为css文件
# npx lessc <需要转换的文件路径> <转换后的文件路径>
npx lessc ./index.less ./dist/index.css

(2) 在webpack中使用less-loader

1
npm i less-loader -D

然后在配置文件中使用即可:

上述是对less文件的操作,如果是sass/scss文件的话,可以用sass-loader这个工具。

postcss-loader

Postcss是一个通过JS转换样式的工具,它可以帮助我们进行一些样式的兼容性处理,比如添加浏览器前缀,压缩样式等。

单独使用postcss

Postcss也可以单独作为一个工具进行使用,操作如下:

(1) 安装postcss-cli

1
npm i postcss-cli -D

(2) 编写配置文件postcss.config.js

1
2
3
4
5
6
module.exports = {
plugins: [
// 插件预设,需要自行安装: npm i postcss-preset-env -D
require('postcss-preset-env'), // 必须使用require导入
],
};

(3) 使用postcss

1
2
# npx postcss 目标路径 -o 输出路径
npx postcss index.css -o css/main.css

在webpack中使用postcss

在webpack中,我们可以用postcss-loader这个工具来使用Postcss。

1
npm i postcss-loader -D

注意:postcss需要有对应的插件才能起对应的效果,我们直接使用是没用的。

如果我们想要为样式添加前缀,需要使用autoprefixer插件。

我们需要先对其进行安装:

1
npm i autoprefixer -D

接下来就是要让postcss-loader能够使用autoprefixer这个插件了,有两种方式:

(1) 通过UseEntryoptions的配置项来配置:

(2) 单独创建postcss配置文件

  • 创建postcss.config.js文件。

  • postcss.config.js文件中添加如下内容。

1
2
3
module.exports = {
plugins: [require('autoprefixer')],
};

使用postcss预设插件

上面我们使用postcss时,如果我们想为样式添加浏览器兼容性前缀,就需要下载一个autoprefixer插件,并且需要在plugins配置项中添加,但我们可能需要用到许多插件,比如说将color()函数转化为兼容性的rgba()函数,我们总不能每一个都安装再配置,一个项目的话可能还好说,但如果多个项目都用到相同的部分了,我们总不能每个项目都再安装一遍。

postcss官方为我们提供了一个postcss-prset-env预设插件,它集成了很多我们常用的插件,我们在开发时,是需要使用它即可,不用再单独配置了,如果有特殊情况的话另说。

(1) 安装postcss-preset-env插件

1
npm i -D postcss-preset-env

(2) 配置postcss-preset-env插件

1
2
3
module.exports = {
plugins: [require('postcss-preset-env')],
};

在我们配置插件的时候,用一种简写,只需要写字符串即可,postcss会自动在node_modules中寻找对应的插件。

如:

1
2
3
module.exports = {
plugins: ['postcss-preset-env'],
};

webpack打包图片

我们在项目开发中,会使用到图片,我们可能会在css中使用图片,也可能以模块的形式import引入图片,前面我们提到,webpack默认是只对js会进行处理,其他的文件类型需要进行loader转换为webpack的js模块系统能够处理的形式,所以对于图片,我们也需要进行一些特殊处理。

在当前webpack版本(v5.99.9)测试中,在css中使用图片(background: url(./img.png) no-repeat;),是可以直接被以源文件的形式打包的,不需要进行额外的配置。

在webpack5之前,我们处理图片需要用到一些loader:

loader 说明
raw-loader 将文件作为字符串导入
url-loader 将文件作为数据 URI 内联到捆绑包中
file-loader 将文件发送到 output 目录

在webpack5,我们可以使用Asset Modules类型替换上面这些加载器。

Asset Modules有四种模块类型:

模块类型 说明 适用场景
asset/resource 发送一个单独的文件并导出 URL,之前通过使用 file-loader 实现 适用于大文件(如图片、字体)
asset/inline 导出一个资源的 data URI,之前通过使用 url-loader 实现 适用于小文件(如图标)
asset/source 导出资源的源代码,之前通过使用 raw-loader 实现 适用于文本文件(如 TXT、SVG)
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择,之前通过使用 url-loader,并且配置资源体积限制实现 智能选择 resource 或 inline,最常用的通用资源处理方式

Asset Module在webpack的配置文件中配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
},
],
},
// 处理图片资源
{
test: /\.(png|jpe?g|svg|gif)$/i,
type: 'asset',
},
];
}

对于图片的默认打包路径,是在output.path指定的目录下,图片名会根据图片的hash值进行重命名,我们也可以手动指定图片的打包路径,有两种方式:

(1) 通过output.assetMoudleFilename配置。

1
2
3
4
5
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
assetModuleFilename: 'images/[hash][ext]'
}

(2) 在Asset Modulegenerator中配置。

1
2
3
4
5
6
7
8
9
10
11
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext]',
},
},
];
}

上面我们看到对于文件的输出路径,我用了[hash][ext]这些占位符,通过这些占位符可以动态生成文件名。

这里给出一些占位符的描述:

占位符 说明 示例输入 示例输出
[name] 文件的基本名称(就是源文件名,不包含扩展名) src/images/logo.png logo
[file] 文件的完整路径,含扩展名 src/images/logo.png src/images/logo.png
[query] 资源导入时的查询参数(带问号) src/images/logo.png?size=large ?size=large
[fragment] 片段标识符(带井号) src/images/logo.png#primary #primary
[base] 文件名(含扩展名) src/images/logo.png logo.png
[ext] 文件扩展名(带点号) logo.png .png
[hash] 文件内容哈希值 任意文件 3c8d8d9e7823e45f
[path] 文件相对路径(不含文件名) src/images/logo.png src/images/

对于[hash]占位符,由于生成的hash值可能太长,不方便查看,所以我们可以使用[hash:count]来指定hash值的长度,如:[hash:4],即只取前4位。

url-loader的limit效果

在我们开发过程中,往往希望小的图片转化为base64,大的图片直接使用,这样可以减少图片的请求,提高性能。

在上述Asset Module的模型类型介绍中,我们可以看出,asset类型可以实现上述效果,而在之前的url-loader中,我们可以设置limit属性,来达到上述效果。

在默认情况下,webpack对于小于8kb的文件,将会视为inline模块类型,否则会被视为resource模块类型,即小于8kb大小的文件,会被转为base64的格式嵌入到文件中,而大于8kb的文件,会发送一个单独的文件,即将源文件复制一份。

但我们也可以自定义文件判断的这个大小,通过Asset Module的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
test: /\.(png|jpe?g|svg|gif)$/i,
type: 'asset',
generator: {
filename: 'images/[hash:4][ext]'
},
parser: {
dataUrlCondition: {
// inline 和 resource 之间切换的界限变为 10kb
maxSize: 10 * 1024
}
}
}

在webpack中使用Babel

Babel是一个工具链,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容的JavaScript代码。

Babel本身可以单独使用,也可以和webpack一起使用。

单独使用Babel

我们需要安装@babel/core@babel/cli,一个是核心代码,一个是可以让我们在命令行使用babel命令。

1
npm install @babel/core @babel/cli -D

在命令行使用babel命令:

1
2
3
4
5
# npx babel 文件夹/文件 --out-dir 转换后的文件夹
npx babel src --out-dir dist # 转换src文件夹下的所有文件到dist文件夹下

# npx babel 文件夹/文件 --out-file/-o 转换后的文件
npx babel src --out-file dist/bundle.js # 转换src文件夹下的所有文件到一个bundle.js文件中

Babel使用插件

Babel本身的核心功能有限,它主要是作为转换器引擎,本身并不包含任何具体的语法转换规则或功能,它需要通过插件来完成这些功能。

这里介绍几个插件:

插件 说明
@babel/plugin-transform-arrow-functions 将 ES2015 箭头函数编译为 ES5
@babel/plugin-transform-block-scoping 将 ES2015 块范围(const 和 let)编译为 ES5

使用插件方式:

1
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping

Babel配置文件

对于上面的操作,每次我们使用,都需要写一大推配置项,而且上面的命令也只是使用了两个插件,如果要使用更多的插件,那么就比较麻烦了,这个时候我们可以使用配置文件来优化我们的操作。

我们可以看一下官方文档中关于配置文件的说明:https://babeljs.io/docs/configuration

由于我们测试的是对于项目的单个部分,我们就创建.babelrc.json配置文件了,填写格式如下:

1
2
3
4
5
6
{
"plugins": [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping"
]
}

但就如在postcss是提到的,我们总不能一个个的添加插件,babel官方给我们也提供了一个预设:@babel/preset-env

对于预设的配置,我们需要使用persets项进行配置:

1
2
3
{
"presets": ["@babel/preset-env"]
}

同时,针对于预设的命令行操作也需要进行修改:

1
npx babel src --out-dir dist --presets=@babel/preset-env

babel-loader

在webpack中使用Babel,需要使用babel-loader进行配置:

首先需要安装babel-loader

1
npm i babel-loader -D

然后在webpack配置文件中进行配置:

1
2
3
4
5
6
7
8
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'babel-loader'
}
]
}

通过babel-loader使用插件和预设

有两种方式:在UseEntry的options中进行配置和通过配置文件的方式进行配置。

通过配置文件的方式进行配置和前面一样,这里写一下options的写法:

配置插件
1
2
3
4
5
6
7
8
9
10
11
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'babel-loader',
options: {
plugins:['@babel/plugin-transform-arrow-functions','@babel/plugin-transform-block-scoping']
}
}
]
},
配置预设
1
2
3
4
5
6
7
8
9
10
11
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
]
}

webpack处理Vue代码

Vue代码明显不是js语言,我们必然需要一个loader处理Vue代码,这个loader就是[vue-loader](https://vue-loader.vuejs.org/zh/)

首先当然还是对其进行安装:

1
npm i vue-loader - D

但想要使用vue-loader对vue文件进行处理,还需要安装@vue/compiler-sfc,因为vue-loader会使用@vue/compiler-sfc中的方法。

只需要安装@vue/compiler-sfc即可:

1
npm i -D @vue/compiler-sfc

同时我还需要在plugins模块引入vue-loader中的VuePluginLoader插件:

1
2
3
4
5
// 引入VuePluginLoader插件
const { VueLoaderPlugin } = require('vue-loader');

// 在plugins数组中添加VuePluginLoader插件
plugins: [new VueLoaderPlugin()];

注意:我们还需要安装vue,因为我们需要为打包的文件提供一个能运行的vue环境,所以我们仍需进行:npm i vue操作

resolve模块解析

resolver是一个帮忙寻找绝对路径的库,resolver 帮助 webpack 从每个 require/import 语句中,找到需要引入到 bundle 中的模块代码。 当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。

webpack能解析三种文件路径:

(1) 绝对路径

1
2
3
import '/home/me/file';

import 'C:\\Users\\me\\file';

由于已经获得文件的绝对路径,因此不需要再做进一步解析。

(2) 相对路径:

1
2
import '../src/file1';
import './file2';

在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径。

(3) 模块路径

1
2
import 'module';
import 'module/lib/file';

resolve.modules

resolve.modules中指定的所有目录中检索模块,默认值是['node_modules'],所以对于模块路径,默认会从node_modules中检索,我们也可以自行指定,但其实也没啥必要。

resolve.alias

我们也可以通过配置别名的方式来替换初始模块路径,比如说我们有一个嵌套层级极深的文件,我们想要引入顶层文件,无论是使用相对路径还是绝对路径,填写起来都过于麻烦,举个例子:

1
2
3
4
5
6
7
8
// src/utils/time.js

// src/view/home/components/index.js
// 我们需要在这个index.js文件中引入time.js文件
// 使用相对路径
import time from '../../../utils/time.js';
// 使用绝对路径
import time from '/src/utils/time.js';

经过上面这个例子,我们发现无论是相对路径还是绝对路径,写起来都过于冗长,我们可以通过webpack中的resolve.alias属性来解决这个问题:

1
2
3
4
5
6
7
resolve: {
alias: {
// 这里直接精确到文件了,也可以填写文件夹,再进入文件即可
// key为别名,value为文件路径
'utilsTime': path.resolve(__dirname, './src/utils/time.js')
}
}

引入time.js文件。

1
import time from 'utilsTime';

resolve.mainFiles

但我们以路径的形式引入,但只引入到文件夹,webpack并不知道要引入该文件夹下的哪一个文件,这个时就可以通过resolve.mainFiles属性来解决。

1
2
3
4
5
resolve: {
// 表示默认去寻找文件夹下的index.js文件
// 默认配置就是index.js
mainFiles: ['index.js'];
}

其实mainFiles中配置的的文件后缀名也可以省略,这与resolve.extensions属性有关。

resolve.extensions

当我们引入文件时,我们不想写后缀名,想让webpack自动去寻找文件,比如:src/utils/run.js,我们引入时只写import run from './utils/run',要实现这个功能,需要配置resolve.extensions属性。

1
2
3
4
5
6
resolve: {
// 默认配置就是 ['.js']
extensions: ['.js'];
// 我们也可以配置多个项
// extensions: ['.js', '.ts'] // 这样在找不到js文件是,就会找ts文件了
}

webpack插件

webpack官方对plugins的描述:

从上图我们可以看出,loader可以做的事情,plugins也可以做的事情,甚至还能做一些优化、压缩、打包等等事情。

我们先介绍几个常用的webpack插件,先使用一下:

HtmlWebpackPlugin

HtmlWebpackPlugin可以生成一个html文件,并自动引入打包后的js文件。

首先我们需要先安装html-webpack-plguin

1
npm i -D html-webpack-plugin

然后在webpack.config.js配置文件中进行配置:

1
plugins: [new HtmlWebpackPlugin()];

再次进行打包操作后就会在打包后的文件夹中生成一个index.html文件,并自动引入打包后的js文件。

对于HtmlWebpackPlugin生成的html模板,我们可能对其中的内容不太满意,我们有时可能希望使用我们自定义编写的文件,这个时候就需要对HtmlWebpackPlugin进行一些配置了。

自定义HTML模板

首先我们需要先编写一个html文件,这一步自己操作即可。

然后在webpack.config.js中配置HtmlWebpackPlugin,如下:

1
2
3
4
5
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
}),
];

然后就可以进行打包测试了。

自定义模板数据填充

对于我们编写的HTML模板,我们可以通过EJS的模板语法占位并进行填充,从这里我们可以看出,HtmlWebpackPlugin是通过EJS模板引擎来处理HTML模板的。

首先我们在模板中定义一个变量,对于这个变量,一定要通过htmlWebpackPlugin.options中获取,比如:

1
2
<!-- title为插件中配置的变量名 -->
<title><%= htmlWebpackPlugin.options.title %></title>

我们再看一下 webpack.config.js 中的配置:

1
2
3
4
5
6
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
title: 'testTitle',
}),
];

CleanWebpackPlugin

CleanWebpackPlugin这个插件的作用是:在打包之前,先删除之前已打包的目录(如果存在),然后重新打包。

安装:

1
npm i -D clean-webpack-plugin

配置如下:

1
plugins: [new CleanWebpackPlugin()];

output.clean

从webpack5.20版本开始,我们不需要再安装clean-webpack-plugin插件了,直接在webpack.config.js中的output中添加clean: true配置即可:

1
2
3
4
5
output: {
clean: true,
filename: 'budle.js',
path: path.resolve(__dirname, './dist')
}

DefinePlugin

DefinePlugin是一个webpack内置的插件,用于在打包时定义全局变量。

1
2
3
4
5
plugins: [
new DefinePlugin({
test: "'test'",
}),
];

然后就可以在开发代码中使用全局变量test了。

注意:如果我们设置的全局变量值为字符串的话,需要用字符串嵌套一层,或使用JSON.stringify()转换一下,否则会将单层字符串中的值解析为变量,这是因为插件的工作机制是执行 代码文本替换,而不是直接注入变量值。

EnvironmentPlugin

EnvironmentPlugin是基于DefinePlugin的一个封装,就是DefinePlugin的一个子类,用于处理环境变量。

EnvironmentPlugin也是一个webpack内置的插件,EnvironmentPlugin 接受一个 key 数组或一个将其 keys 映射到其默认值的对象。

其实就是将EnvironmentPlugin中的key值,映射到process.env中的值。

如果缺少,它将搜索配置中提供的默认值。如果既未定义环境变量也未定义默认值,则会引发错误:“EnvironmentPlugin - ${key} 环境变量未定义 ”。

如下:在不进行其他操作的前提下,我们通过数组的方式,来配置需要注入的变量,但我们并未设置起默认值,在打包的时候就会出现上述错误,但是还是能运行,只不过通过process.env获取的值为undefined。

1
plugins: [new EnvironmentPlugin(['NODE_ENV', 'DEBUG'])];

设置默认值:

1
2
3
4
5
6
plugins: [
new EnvironmentPlugin({
NODE_ENV: 'development',
DEBUG: false,
}),
];

通过上面设置我们就可以正常获取了:

1
console.log(process.env.NODE_ENV); // development

上文我们说了,EnvironmentPlugin是DefinePlugin的子类,所以通过EnvironmentPlugin设置环境变量,实际上也是通过DefinePlugin实现的。

对于上述的操作,通过DefinePlugin实现如下:

1
2
3
4
5
6
plugins: [
new DefinePlugin({
'process.env.NODE_ENV': '"development"',
'process.env.DEBUG': 'false',
}),
];

DotenvPlugin

DotenvPlugin是第三方插件,也是与环境变量有关的,用于加载.env文件,我们可以在.env文件中定义环境变量。

首先我们需要先安装dotenv-webpack

1
npm i -D detenv-webpack

然后在webpack.config.js中添加如下配置:

1
plugins: [new DotenvWebpackPlugin()];

然后我们就可以直接创建一个.env文件(这与配置项path的默认值有关),并定义环境变量:

1
VUE_BASE_URL=/api/

然后就可以在开发代码中任意位置使用通过process.env.VUE_BASE_URL来获取环境变量了。

DotenvPlugin配置项

参数 描述 默认值 场景示例
path .env文件的路径 ‘./.env’ path: './.env.development'
safe 安全模式(safe),验证.env文件包含所有.env.example中的变量 false
allowEmptyValues 是否safe下允许空字符串。如果为 false,则如果任何 env 变量为空,将引发错误(但前提是启用了安全模式) false
expand 否解析变量中的嵌套值 false BASE_URL=example.com API_URL=$BASE_URL/api
prefix 要在环境变量名称之前使用的前缀 ‘process.env.’ prefix: 'import.meta.env.'
systemvars 如果您更愿意加载所有系统变量,请设置为 true(对于 CI 目的很有用) false
silent 如果为 true,则将禁止显示所有警告 false
defaults 添加对 dotenv-defaults 的支持 false
ignoreStub 覆盖是否对 process.env 进行存根的自动检查 false

通过DotenvPlugin加载不同的.env.[environemt]配置文件

在项目开发中,我们通常会定义多个环境变量文件,里面配置相同项但不同值,以期许在不同的环境下使用不同的配置,我们可以利用DotenvPluginpath实现。

对于.env.development.env.production两个文件,只需要通过process.env.NODE_ENV环境变量就能指定使用哪个文件。`

注意:DotenvWebpackPlugin 默认不会覆盖已存在的 process.env.NODE_ENV 值,即通过mode设置过后,在.env中的NODE_ENV配置就不生效了。这是由 Node.js 环境变量的特性决定的,而不是插件本身的限制。

上述说的是默认不会覆盖,就是说可以覆盖,但我建议不要覆盖,这样可能导致构建系统行为异常,这里我就不说怎么操作覆盖了。

我们需要借助--env这个命令行参数,同时需要把配置文件改为导出一个函数,这样能接收到--env的参数:

1
2
3
4
5
6
module.exports = (env) => {
console.log(env); // env中可以获取到--env传入的参数
return {
// 配置内容
};
};

对于这个命令行参数填写也有一定的要求:

1
2
3
4
# windows cmd 填写
# set NODE_ENV=模式名 && npx webpack --env 键名=值(环境名)
set NODE_ENV=development && npx webpack --env env=test
# set NODE_ENV=development进行这步操作以为了在node环境下获取到process进程中的自定义的NODE_ENV变量值

接下来给出一个大概的配置文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const path = require('path');
const DotenvWebpackPlugin = require('dotenv-webpack');

module.exports = (env) => {
// 环境
const environment = env?.env || process.env.NODE_ENV || 'deveploment';
// 动态的.env配置文件
const envFilePath = path.resolve(__dirname, `.env.${environment}`);

return {
mode: 'development',
entry: './src/index.js',
output: {
clean: true,
filename: 'budle.js',
path: path.resolve(__dirname, './dist'),
},
resolve: {
alias: {
utilsTime: path.resolve(__dirname, './src/utils/time.js'),
},
extensions: ['.js'],
},

plugins: [
new DotenvWebpackPlugin({
path: envFilePath,
systemvars: true,
safe: true,
}),
],
};
};

使用cross-env解决跨环境问题

[cross-env](https://www.npmjs.com/package/cross-env)是一个运行跨平台设置和使用环境变量的脚本。

因为我们在window、linux中运行命令时,环境变量的设置方式不同:

  • windows cmdset NODE_ENV=development

  • powershell$env:NODE_ENV="development"

  • linuxNODE_ENV=developmentexport NODE_ENV=development

cross-env的出现可以实现跨环境设置环境变量,即一种命令可以在所有环境下运行。

安装cross-env

1
npm i cross-env -D

命令行使用:

1
2
# npx cross-env 环境变量 其他命令
npx cross-env NODE_ENV=production webpack

上述命令可以在所有环境都能生效运行。

ProvidePlugin

ProvidePlugin是一个webpack的插件,用于自动加载模块,而无需在每个模块中手动引入,通过ProvidePlugin能帮助我们实现shimming(垫片)的效果。

ProvidePlugin的使用场景:

比如说我们在项目想使用axios这一个库,但我不想引入,同时我又没有对其进行全局设置,那么我们就可以使用ProvidePlugin解决这个问题。

1
2
3
4
new ProvidePlugin({
axios: 'axios', // 当代码中出现 axios 标识符时,webpack 会自动注入 const axios = require('axios'); 或 import axios from 'axios';
get: ['axios', 'get'], // 当代码中出现 get 标识符时,webpack 会自动注入 const get = require('axios').get; 或 import { get } from 'axios';
});

webpack打包后注释模块的单独提取

在生产环境下,在我们引入第三方库进行打包时,我们会发现打包产物中可能会有一个xxx.LICENSE.txt文件,这主要涉及到法律合规性和开源许可证的要求,也时对开源作者的基本尊重。

webpack会自动帮我们进行提取,这与terser-webpack-plugin插件有关,在安装webpack的时候会自动帮我们进行安装。

我们不需要进行配置,因为在生产环境下,webpack会自动进行配置,内部配置如下:

1
2
3
4
5
6
7
8
optimization: {
minimizer: [
new TerserPlugin({
// 开启注释模块的单独提取
extractComments: true,
}),
];
}