前言

这是这个系列的第三篇了,这篇讲讲一些Webpack配置,还有常见的扩展

Webpack配置项

entry入口

入口起点指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,Webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的,并进行相应的解析和打包操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 简写
module.exports = {
entry: './path/to/my/entry/file.js'
};

// 对象写法,一般针对多入口的情况, 属性名代表chunk的名称
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js'
}
};

output出口

配置打包后的输出文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 把打包后的文件bundle.js输出到dist目录中
module.exports = {
output: {
filename: 'bundle.js',
}
};


// 多入口写法
// 多个入口会被打包成多个js文件,不能只写一个固定的文件名
// 要使用动态的文件名进行输出
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};

mode

告知 webpack 使用的打包模式,有三个可取值

  • development
  • production
  • none

其实就是三个模式下webpack有不同的配置而已,比如production模式下会使用一些代码压缩和混淆的插件。

context

设置context会影响入口和loaders的解析,入口和loaders的相对路径会以context的配置作为基准路径。这样,你的配置会独立于CWD(current working directory 当前执行路径)

当前执行路径是什么呢,就是你打开控制台时的那一串文件路径

1
E:\Workspaces\Project\WebPro\WebpackTest>

比如现在我的webpack.config.js是这样的

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
let path = require("path");
let MyPlugin = require("./src/plugins/MyPlugin");
let FileListPlugin = require("./src/plugins/FileListPlugin");
module.exports = {
entry: "./src/index.js",
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000
},
module: {
rules: [{
test: /\.css$/,
use: ["./src/loaders/style-loader"]
}, {
test: /\.(png)|(jpg)|(gif)$/, use: [{
loader: "./src/loaders/img-loader.js",
options: {
limit: 3000, // 3000字节以上使用图片,3000字节以内使用base64
filename: "img-[contenthash:5].[ext]"
}
}]
}]
},
plugins: [
new MyPlugin(),
new FileListPlugin()
]
}

在使用了context后,就可以对入口和loader的文件路径进行一些改变

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
34
35
36
let path = require("path");
let MyPlugin = require("./src/plugins/MyPlugin");
let FileListPlugin = require("./src/plugins/FileListPlugin");
module.exports = {
// 修改入口路径
entry: "./index.js",
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000
},
context: path.resolve(__dirname, "src"),
// 修改loader路径
module: {
rules: [{
test: /\.css$/,
use: ["./loaders/style-loader"]
}, {
test: /\.(png)|(jpg)|(gif)$/, use: [{
loader: "./loaders/img-loader.js",
options: {
limit: 3000, // 3000字节以上使用图片,3000字节以内使用base64
filename: "img-[contenthash:5].[ext]"
}
}]
}]
},
plugins: [
new MyPlugin(),
new FileListPlugin()
]
}

library和libraryTarget

这两个变量用于暴露自执行函数的执行结果,配置在output下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let path = require("path");
let MyPlugin = require("./src/plugins/MyPlugin");
let FileListPlugin = require("./src/plugins/FileListPlugin");
module.exports = {
// 修改入口路径
entry: "./index.js",
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
// 控制暴露的变量名
library: "indexVal",
// 控制怎么暴露
libraryTarget: "var"
},
// ...其他配置同上
}

打包结果暴露到indexVal上,使用var暴露

image-20201222214510609

其他可用的值有:

target

设置打包结果最终要运行的环境,常用值有

  • web: 打包后的代码运行在web环境中
  • node:打包后的代码运行在node环境中

改改webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let path = require("path");
let MyPlugin = require("./src/plugins/MyPlugin");
let FileListPlugin = require("./src/plugins/FileListPlugin");
module.exports = {
// 修改入口路径
entry: "./index.js",
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
library: "indexVal",
libraryTarget: "var"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000
},
context: path.resolve(__dirname, "src"),
target: "web"
}

在index.js里加一行

1
2
import fs from "fs";
console.log(fs);

运行webpack进行打包就会报错

1
2
ERROR in ./index.js 4:0-20
Module not found: Error: Can't resolve 'fs' in 'E:\Workspaces\Project\WebPro\WebpackTest\src'

这个配置会影响一些内置模块的解析,如果设置的target是web,是不能使用node的内置模块的

如果要对这段代码进行打包,需要把target改成node

打包结果

image-20201222222258374

就是非常普通的导出了一个require(“fs”)

noParse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: ["./loaders/style-loader"]
},
{
test: /\.(png)|(jpg)|(gif)$/, use: [{
loader: "./loaders/img-loader.js",
options: {
limit: 3000, // 3000字节以上使用图片,3000字节以内使用base64
filename: "img-[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
}

不解析正则表达式匹配的模块,通常用它来忽略那些大型的单模块库,以提高构建性能

简单来说,就是匹配到的模块会在读取文件内容后直接把内容添加到最终生成的assets中,跳过AST解析和依赖查找等步骤,对一些已经是打包后代码的大型库可以使用这个选项

resolve

resolve的相关配置主要用于控制模块解析过程,详情可以看这里

modules

1
modules: ["node_modules"]  //默认值

这个配置告诉webpack在使用下面的导入语句时该去哪里查找依赖

1
import $ from "jquery";

extensions

这个配置告诉webpack在导入模块时没有后缀该怎么自动补全后缀

1
extensions: [".js", ".json"]  //默认值
1
import add from "./util/add";

这里不需要写后缀就是因为webpack会尝试进行后缀补全,如果能匹配到对应的文件就会进行相应的读取解析操作

alias

alias是别名的意思,用于代替一个路径

1
2
3
4
5
6
7
8
9
10
module.exports = {
entry: "./index.js",
mode: "development",
// ...
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
}
}

在index.js中就可以使用@了

1
import add from "@/add.js";

在大型系统中,源码结构往往比较深和复杂,别名配置可以让我们更加方便的导入依赖

externals

有时一些外部库我们是通过CDN的方式来引入,那么我们就不希望让这些库出现在bundle中,这时候我们就要使用这个配置了

修改dist/index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
<script src="./bundle.js"></script>
</body>
</html>

src/index.js

1
2
import jquery from "jquery";
console.log(jquery);

webpack.config.js

1
2
3
4
5
6
module.exports = {
// ...
externals: {
jquery : "$"
}
}

康康打包结果

image-20201222235001970

在使用了这个配置后,不会再把整个jquery打包到打包结果里了,而是直接导出一个$,这个$就是通过cdn引入jquery时绑定在页面上的值

常用扩展

clean-webpack-plugin

A webpack plugin to remove/clean your build folder(s).

这个插件用于清除你的output文件夹,一般用于清除上次打包的文件

html-webpack-plugin

Plugin that simplifies creation of HTML files to serve your bundles

这个插件可以生成一个html,其实就是自动生成一个html引用了打包结果而已

修改webpack.config.js

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 修改入口路径
entry: {
index : "./index.js"
},
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[chunkhash:5].js",
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000
},
context: path.resolve(__dirname, "src"),
target: "node",
// 修改loader路径
module: {
rules: [
{
test: /\.css$/,
use: ["./loaders/style-loader"]
},
{
test: /\.(png)|(jpg)|(gif)$/, use: [{
loader: "./loaders/img-loader.js",
options: {
limit: 3000,
filename: "img-[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
// 清除dist目录
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
// 使用的模板
template: "./public/index.html",
// 生成的文件名
filename: "index.html",
// 添加哪些chunk到生成的HTML里
chunks: ["index"]
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
},
externals: {
jquery : "$"
}
}

新建public/index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>

</body>
</html>

打包,会在dist目录下生成一个index.html文件

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script defer src="index.1f60d.js"></script></head>
<body>

</body>
</html>

顺带一提,如果你报了这个错误

1
The 'compilation' argument must be an instance of Compilation

那是因为html-webpack-plugin旧版本对webpack5不兼容,可以安装5.0.0-alpha.9这个版本来解决

copy-webpack-plugin

Copies individual files or entire directories, which already exist, to the build directory.

这个插件用于复制静态文件到output文件夹里,参考下面的例子

src/public/index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
<img src="../assets/img.png">
</body>
</html>

webpack.config.js

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// 修改入口路径
entry: {
index: "./index.js"
},
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[chunkhash:5].js",
},
// 开发服务器配置
devServer: {
// 告诉服务器内容的来源
contentBase: path.resolve(__dirname, 'dist'),
// 服务器使用哪个端口
port: 9000,
// 自动打开
open : true
},
context: path.resolve(__dirname, "src"),
target: "node",
// 修改loader路径
module: {
rules: [
{
test: /\.css$/,
use: ["./loaders/style-loader"]
},
{
test: /\.(png)|(jpg)|(gif)$/, use: [{
loader: "./loaders/img-loader.js",
options: {
limit: 3000,
filename: "img-[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
// 清除dist目录
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
// 使用的模板
template: "./public/index.html",
// 生成的文件名
filename: "html/index.html",
// 添加哪些chunk到生成的HTML里
chunks: ["index"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: "./assets",
to: "./assets"
},
],
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}},
externals: {
jquery : "$"
}
}

打包后生成的dist/html/index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script defer src="../js/index.1f60d.js"></script></head>
<body>
<img src="../assets/img.png">
</body>
</html>

dist目录如下

image-20201223184805199

如果没有使用copy-webpack-plugin,就不会生成assets文件夹

本地开发服务器

想必你已经注意到了,在开发阶段,从编写代码到查看效果的过程非常繁琐,有下面几个步骤

  1. 编写代码
  2. 控制台运行命令完成打包
  3. 刷新页面查看效果

而且有时候,我们希望把最终生成的代码和页面部署到服务器环境上来模拟真实环境

为了解决这些问题,webpack官方提供了一个库webpack-dev-server,它不是plugin也不是loader,而是一个单独的库

安装

1
cnpm install webpack-dev-server -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// 开发服务器配置
devServer: {
// 告诉服务器内容的来源
contentBase: path.resolve(__dirname, 'dist'),
// 服务器使用哪个端口
port: 9000,
// 自动打开
open : true
},
}

启动本地开发服务器

1
webpack serve

访问http://localhost:9000/html/index.html,就可以看到效果了

dist/html/index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script defer src="../js/index.1f60d.js"></script></head>
<body>
<img src="../assets/img.png">
</body>
</html>

文件处理

这里我们介绍file-loader和url-loader

file-loader

The file-loader resolves import/require() on a file into a url and emits the file into the output directory.

url-loader

A loader for webpack which transforms files into base64 URIs.

安装file-loader和url-loader

1
cnpm install file-loader url-loader -D

配置一下

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// 修改入口路径
entry: {
index: "./index.js"
},
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[chunkhash:5].js",
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000,
open : true,
},
context: path.resolve(__dirname, "src"),
target: "node",
// 修改loader路径
module: {
rules: [
{
test: /\.css$/,
use: ["./loaders/style-loader"]
},
{
test: /\.(png)|(gif)|(jpg)$/,
use: [{
loader: "url-loader",
options: {
limit: 10 * 1024, //只要文件不超过 100*1024 字节,则使用base64编码,否则,使用file-loader进行处理
// options配置会被传给file-loader,这个配置是给file-loader用来生成文件名用的
name: "imgs/[name].[hash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
// 清除dist目录
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
// 使用的模板
template: "./public/index.html",
// 生成的文件名
filename: "html/index.html",
// 添加哪些chunk到生成的HTML里
chunks: ["index"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: "./assets",
to: "./assets"
},
],
}),
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}},
externals: {
jquery : "$"
}
}

修改src/index.js

1
2
3
4
5
6
7
8
9
import src from "./assets/img.png";
let img = document.createElement("img");
img.src = src;
document.body.appendChild(img);
console.log(img);
import jquery from "jquery";
console.log(jquery);


打开页面,你会发现报了这样一个错

image-20201223211157921

那是图片没有打包成功吗,其实不是,我们康康文件目录,然后看看img的src是什么

image-20201223211436828

文件目录

image-20201223211545404

看出问题在哪了嘛,在index.js中有这样一句话

1
import src from "./assets/img.png";

这个图片的解析是通过url-loader的,url-loader会把这个图片输出到img/下,然后返回对应的路径

这一点可以通过打包文件验证

image-20201223212408376

可以看到这个文件最后被转化成了一个文件路径,值是

1
__webpack_require__.p + "imgs/img.e7819.png"

所以这就是问题所在了,我们打包后的js文件在这个路径下被执行

1
http://localhost:9000/html/index.html

所以__webpack_require__.p + "imgs/img.e7819.png"http://localhost:9000/html/index.html一拼接,就变成了了这样(__webpack_require__.p默认是””)

1
http://localhost:9000/html/imgs/img.e7819.png

这种问题发生的根本原因就是模块中的路径来自于某个loader或plugin,当产生路径时,loader或plugin只有相对于dist目录的路径,并不知道该路径将在哪个资源中使用,从而无法确定最终正确的路径

那么这个问题怎么解决呢,其实也挺简单,看到那个__webpack_require__.p了嘛,我们修改它就可以了

publicPath

官方文档看这里

webpack 提供一个非常有用的配置,该配置能帮助你为项目中的所有资源指定一个基础路径,它被称为公共路径(publicPath)。

publishPath只是配置了一个字符串,但是一些loader和plugin会在有它存在时,对路径进行相应的处理,比如说上面的url-loader会把它拼在生成的文件名的前面

我们现在配置publishPath为”/“

打包结果中多了这样一个东西

1
2
3
(() => {
__webpack_require__.p = "/";
})();

现在这一个路径的结果变成了

1
"/" + "imgs/img.e7819.png"

访问正常√

image-20201223215135537

webpack内置插件

DefinePlugin

全局常量定义插件,使用该插件通常定义一些常量值,这样一来,在源码中,我们可以直接使用插件中提供的常量,当webpack编译完成后,会自动替换为常量的值

BannerPlugin

它可以为每个chunk生成的文件头部添加一行注释,一般用于添加作者、公司、版权等信息

ProvidePlugin

自动加载模块,而不必到处 import 或 require

修改index.js

1
2
3
4
console.log(PI);
console.log(VERSION);
console.log(DOMAIN);
console.log($);

修改webpack.config.js

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require("webpack");
module.exports = {
// 修改入口路径
entry: {
index: "./index.js"
},
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[chunkhash:5].js",
publicPath: "/"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000,
open : true,
},
context: path.resolve(__dirname, "src"),
target: "node",
// 修改loader路径
module: {
rules: [
{
test: /\.css$/,
use: ["./loaders/style-loader"]
},
{
test: /\.(png)|(gif)|(jpg)$/,
use: [{
loader: "url-loader",
options: {
limit: 10 * 1024, //只要文件不超过 100*1024 字节,则使用base64编码,否则,交给file-loader进行处理
// options配置会被传给file-loader,这个配置是给file-loader用来生成文件名用的
name: "imgs/[name].[hash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
// 清除dist目录
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
// 使用的模板
template: "./public/index.html",
// 生成的文件名
filename: "html/index.html",
// 添加哪些chunk到生成的HTML里
chunks: ["index"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: "./assets",
to: "./assets"
},
],
}),
new webpack.DefinePlugin({
PI: `Math.PI`, // const PI = Math.PI
VERSION: `"1.0.0"`, // VERSION = "1.0.0"
DOMAIN: JSON.stringify("https://www.sakura-snow.com/")
}),
new webpack.BannerPlugin({
banner: `
hash : [hash]
chunkhash : [chunkhash]
name : [name]
author : SakuraSnow
`
}),
new webpack.ProvidePlugin({
$: 'jquery'
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}},
externals: {
jquery : "$"
}
}

看看打包结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var __webpack_modules__ = (
{
"./index.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
var jquery__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! jquery */ "jquery");
var jquery__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_0__);
// ProvidePlugin自动加载模块
var $ = __webpack_require__(/*! jquery */ "jquery");
// DefinePlugin定义常量定义
console.log(Math.PI);
console.log("1.0.0");
console.log("https://www.sakura-snow.com/");
console.log($);

}),
"jquery": ((module) => {
eval("module.exports = $;\n\n//# sourceURL=webpack:///external_%22$%22?");
})

还是挺容易看懂的,而且莫得啥用orz,康康就行

后记

哇写的真是头秃,下篇写css相关的配置orz,希望不咕