# webpack
中 loader
原理
# loader
介绍
每个loader本质上都是一个函数。在
Webpack 4
之前,函数的输入和输出都必须为字符串;在Webpack 4
之后,loader
也同时支持抽象语法树(AST
)的传递,通过这种方法来减少重复的代码解析。
output=loader(input)
这里的 input
可能是工程源文件的字符串,也可能是上一个 loader
转化后的结果,包括转化后的结果(也是字符串类型)、source map
,以及 AST
对象;output
同样包含这几种信息,转化后的文件字符串、source map
,以及 AST
。如果这是最后一个 loader
,结果将直接被送到 Webpack
进行后续处理,否则将作为下一个 loader
的输入向后传递。
当我们串联地利用多个loader
去转换一个文件时,每个loader
都会链式地顺序执行。在webpack
中,在同一文件存在多个匹配loader
的情况下,各个loader
的执行过程会遵循以下原则:
loader
的执行顺序和配置顺序是相反的,即配置的最后一个loader
最先执行,第一个loader
最后执行。- 第一个执行的
loader
接收源文件的内容作为参数,其他loader
接收前一个执行的loader
的返回值作为参数。最后执行的loader
会返回最终结果。
更进一步我们知道在配置Webpack
时,可以对loader
增加一些配置,比如著名的babel-loader
的简单配置。
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
"plugins": [
"dynamic-import-webpack"
]
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这样一来,简单的loader
写法便不能满足需求,因为编写loader
时,除了编写source
内容,还需要根据开发者配置的options
信息进行构建定制化处理,以输出最后的结果。那么如何获取options
,这时就需要用到loader-utils
模块。
const loaderUtils = require("loader-utils")
module.exports = function(source) {
// 获取开发者配置的options
const options = loaderUtils.getOptions(this)
// some magic
return content
}
2
3
4
5
6
7
对于loader
返回的内容,实际开发中,单纯对content
进行改写并返回改写后的内容,也许是不够的。比如,我们想对loader
处理过程中的错误进行捕获,或者想导出sourceMap
等信息时,该如何做?比如我们想对loader
处理过程中的错误进行捕获,或者想导出sourceMap
等信息时,该如何做呢?这种情况需要loader
中的this.callback
来返回内容。this.callback
中可以传入4
个参数,分别:
error
:当loader
出错时向外抛出一个error
content
:经过loader
编译后需要导出的内容sourceMap
:为方便调试编译后的source map
。ast
:本次编译生成的抽象语法树。之后执行的loader
可以直接使用这个AST
,进而省去重复生成AST
过程。
module.exports = function loader (content, map, meta) {
var callback = this.async();
var result = handler(content, map, meta);
callback(
null, // error
result.content, // 转换后的内容
result.map, // 转换后的 source-map
result.meta, // 转换后的 AST
);
};
2
3
4
5
6
7
8
9
10
使用this.callback
后,我们的loader
代码就会变得更加复杂,同时能够处理更加多样的需求,比如:
module.exports = function(source) {
// 获取开发者配置的options
const options = loaderUtils.getOptions(this)
// some magic
// return content
this.callback(null,content)
}
2
3
4
5
6
7
当使用
this.callback
返回内容时,该loader
必须返回undefined
,这样webpack
就知道该loader
返回的结果在this.callback
中,而不在return
中。这里的this
指向的是一个叫loaderContext
的loader-runner
特有对象。
# 常用loader
css-loader
的作用仅仅是处理CSS
的各种加载语法(@import
和url()
函数等),如果要使样式起作用还需要style-loader
来把样式插入页面。css-loader
与style-loader
通常是配合在一起使用的。url-loader
与file-loader
作用类似,唯一的不同在于用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader
一样返回publicPath
,而小于该阈值时则返回文件base64
形式编码。
TIP
url-loader
和 file-loader
都是 Webpack
中用来处理文件的 loader
,它们的主要区别在于文件大小和处理方式。
file-loader
将文件复制到输出目录中,并返回相对路径,用于最终的JS
文件中的引用。如果文件较大,它通常是首选。当使用 file-loader
处理文件时,文件被复制到输出目录,并生成一个 URL
供最终的 JS
文件中使用。
url-loader
可以将小于指定大小(默认为 8KB
)的文件转换为 Base64 URL
,以减少 HTTP
请求的数量,从而提高页面加载性能。这对于小文件(如图标)非常有用,因为它们可以直接嵌入到最终的 JS
文件中。但对于大文件(会自动交给file-loader
处理), url-loader
不如 file-loader
的性能好。另外,url-loader
也可以与 file-loader
结合使用,以处理大文件。
需要注意的是,使用 url-loader
或 file-loader
时,需要在 Webpack
的配置文件中指定相应的规则,以便它们可以处理相应类型的文件。
以下是 file-loader
和 url-loader
的简单使用示例:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images',
},
},
],
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[name].[ext]',
outputPath: 'images',
},
},
],
},
],
},
};
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
# loader
更多配置
exclude
的含义是,所有被正则匹配到的模块都排除在该规则之外,也就是说node_modules
中的模块不会执行这条规则。该配置项通常是必加的,否则可能拖慢整体的打包速度。
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
exclude: /node_modules/,
}
],
2
3
4
5
6
7
include
代表该规则只对正则匹配到的模块生效。假如我们将include
设置为工程的源码目录,自然而然就将node_modules
等目录排除掉了
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include: /src/,
}
],
2
3
4
5
6
7
resource
与issuer
可用于更加精确地确定模块规则的作用范围。前面介绍的test
、exclude
、include
本质上属于对resource
也就是被加载者的配置,如果想要对issuer
加载者也增加条件限制,则要额外写一些配置。
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
exclude: /node_modules/,
issuer: {
test: /\.js$/,
include: /src/pages/,
},
}
],
2
3
4
5
6
7
8
9
10
11
enforce
用来指定一个loader
的种类,只接收“pre”
或“post”
两种字符串类型的值。Webpack
中的loader
按照执行顺序可分为pre、inline、normal、post
四种类型,上面我们直接定义的loader
都属于normal
类型,inline
形式官方已经不推荐使用,而pre
和post
则需要使用enforce
来指定。其enforce
的值为“pre”
,代表它将在所有正常loader
之前执行,这样可以保证其检测的代码不是被其他loader
更改过的。类似的,如果某一个loader
是需要在所有loader
之后执行的,我们也可以指定其enforce
为“post”
。
TIP
exclude
和include
同时存在时,exclude
的优先级更高- 可以看到,我们添加了
issuer
配置对象,其形式与之前对resource
条件的配置并无太大差异。但只有/src/pages/
目录下面的JS
文件引用CSS
文件,这条规则才会生效;如果不是JS
文件引用的CSS
(比如JSX
文件),或者是别的目录的JS
文件引用CSS
,则规则不会生效。 - 事实上,我们也可以不使用
enforce
而只要保证loader
顺序是正确的即可。配置enforce
主要的目的是使模块规则更加清晰,可读性更强,尤其是在实际工程中,配置文件可能达到上百行的情况,难以保证各个loader
都按照预想的方式工作,使用enforce
可以强制指定loader
的作用顺序。