认识
一、认识
Webpack
中的 loader
本质上是一个函数, 接收文件内容源代码作为入参, 同时返回处理后的结果。
Webpack
的 loader-runner
核心逻辑为: 在 Webpack
进入构建阶段后,首先接受待处理的资源文件路径, 会通过 IO
接口读取文件内容,之后调用 LoaderRunner
并将文件内容以 source
参数形式传递到 Loader
数组,source
数据在 Loader
数组内首先经过pitch
阶段读取资源文件内容再经过normal
阶段处理资源文件内容,最终以标准 JavaScript
代码提交给 Webpack
主流程,以此实现内容翻译功能。
二、语法
2.1 配置语法
module.exports = {
module:{
rules: [
{
test: //,
enforce: 'pre',
use: [{loader: 'xx-loader'}]
}
]
}
}
-
test
: 是一个正则表达式,我们会对应的资源文件根据test
的规则去匹配。如果匹配到,那么该文件就会交给对应的loader
去处理。 -
use
:use
表示匹配到test
中匹配对应的文件应该使用哪个loader
的规则去处理,use
可以为一个字符串,也可以为一个数组。额外注意,如果use
为一个数组时表示有多个loader
依次处理匹配的资源,按照从右往左(从下往上
) 的顺序去处理。 -
enforce
:loader
中存在一个enforce
参数标志着loader
的顺序。enforce
有两个值分别为pre
、post
:-
当我们的
rules
中的规则没有配置enforce
参数时,默认为normal loader
(默认loader
)。 -
当我们的
rules
中的规则配置enforce:'pre'
参数时,我们称之它为pre loader
(前置loader
)。 -
当我们的
rules
中的规则配置enforce:'post'
参数时,我们称之它为post loader
(后置loader
)。
-
2.2 编写语法
function loader(sourceCode, sourceMap?, data?){
console.log(this);
}
loader.pitch = function(remainingRequest, previousRequest, data){
}
module.exports = loader;
-
sourceCode
: 资源输入, 对于第一个执行的Loader
为资源文件的内容;后续执行的Loader
则为前一个Loader
的执行结果,可能是字符串,也可能是代码的AST
结构 -
sourceMap
: 可选参数,代码的sourcemap
结构 -
data
: 可选参数,其它需要在Loader
链中传递的信息,比如posthtml/posthtml-loader
就会通过这个参数传递额外的AST
对象 -
this
: 表示上下文对象, 上下文对象将在运行Loader
时以this
方式注入到Loader
函数。有如下属性:-
this.fs
:Compilation
对象的inputFileSystem
属性,我们可以通过这个对象获取更多资源文件的内容 -
this.resource
: 当前文件路径 -
this.resourceQuery
: 文件请求参数,例如import "./a?foo=bar"
的resourceQuery
值为?foo=bar
-
this.async
: 用于声明这是一个异步Loader
,开发者需要通过async
接口返回的callback
函数传递处理结果 -
this.callback
: -
this._compiler
: 用于访问webpack
的当前Compiler
对象 -
this._compilation
: 用于访问webpack
的当前Compilation
对象 -
this.getOptions(schema)
: 用于获取当前Loader
的配置对象。提取给定的loader
选项,接受一个可选的JSON schema
作为参数 -
this.cacheable(true / false)
:Webpack
默认会缓存Loader
的执行结果直到资源或资源依赖发生变化,开发者需要对此有个基本的理解,必要时可以通过this.cachable
显式声明不作缓存 -
this.emitWarning
: 添加警告 -
this.emitError
: 添加错误信息,注意这不会中断Webpack
运行 -
this.emitFile
: 用于直接写出一个产物文件,例如file-loader
依赖该接口写出Chunk
之外的产物; -
addDependency
: 将dep
文件添加为编译依赖,当dep
文件内容发生变化时,会触发当前文件的重新构建
-
-
pitch
:-
remainingRequest
: 表示剩余需要处理的loader
的绝对路径以!
分割组成的字符串。 -
previousRequest
: 表示pitch
阶段已经迭代过的loader
按照!
分割组成的字符串。 -
data
: 默认是一个空对象{}
, 在normalLoader
与pitch Loader
进行交互正是利用了第三个data
参数。
-
三、编写
3.1 通过 this.cacheable() 取消 Loader 缓存
需要注意,Loader
中执行的各种资源内容转译操作通常都是 CPU
密集型 —— 这放在 JavaScript
单线程架构下可能导致性能问题;又或者异步 Loader
会挂起后续的加载器队列直到异步 Loader
触发回调,稍微不注意就可能导致整个加载器链条的执行时间过长。
为此,Webpack
默认会缓存 Loader
的执行结果直到模块或模块所依赖的其它资源发生变化,我们也可以通过 this.cacheable
接口显式关闭缓存:
module.exports = function(source) {
this.cacheable(false);
// ...
return output;
};
3.2 Loader 通过 this.callback() 返回多个结果
简单的 Loader
可直接 return
语句返回处理结果,复杂场景还可以通过 callback
接口返回更多信息,供下游 Loader
或者 Webpack
本身使用
export default function loader(content, map) {
this.callback(null, content, map);
}
通过 this.callback(null, content, map)
语句,同时返回转译后的内容与 sourcemap
内容。callback
的完整签名如下:
this.callback(
// 异常信息,Loader 正常运行时传递 null 值即可
err: Error | null,
// 转译结果
content: string | Buffer,
// 源码的 sourcemap 信息
sourceMap?: SourceMap,
// 任意需要在 Loader 间传递的值
// 经常用来传递 ast 对象,避免重复解析
data?: any
);
3.3 Loader 通过 this.async() 返回异步结果
涉及到异步或 CPU
密集操作时,Loader
中还可以以异步形式返回处理结果
async function loader(source){
// 1. 调用 this.async() 获取异步回调函数, 此时 Webpack 会将该 Loader 标记为异步加载器, 会挂起当前执行队列直到 callback 被触发;
const callback = this.async();
try{
// 2. 编译模块
const result = await handler(source);
}catch(error){
}
// 3. 编译结束, 调用异步回调 callback 返回处理结果
callback(null,css,map);
}
3.4 Loader 通过 return promise 返回异步结果
涉及到异步或 CPU
密集操作时,Loader
中还可以以异步形式返回处理结果
function loader(source){
return Promise((resolve)=>{
setTimeout(()=>{
resolve(3000);
});
});
}
3.5 通过导出 raw 处理 Loader 二进制资源
默认情况下,资源文件会被转化为 UTF-8
字符串,然后传给 loader
。通过设置 raw
为 true
,loader
可以接收原始的 Buffer
。每一个 loader
都可以用 String
或者 Buffer
的形式传递它的处理结果。complier
将会把它们在 loader
之间相互转换。
有时候我们期望以二进制方式读入资源文件,例如在 file-loader
、image-loader
等场景中,此时只需要添加 export const raw = true
语句即可,如:
module.exports = function (content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// 返 回值也可以是一个 `Buffer`
// 即使不是 "raw",loader 也没问题
};
module.exports.raw = true;