# 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出错时向外抛出一个errorcontent:经过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的作用顺序。