Webpack Loader API
Loader是Webpack中一个重要的概念,可以帮助我们在加载某些类型的文件时进行我们想要的操作,比如自动将所有JavaScript的function外部都加上try catch。
所谓 loader 只是一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this
上下文将由 webpack 填充,并且 loader runner 具有一些有用方法,可以使 loader 改变为异步调用方式,或者获取 query 参数。
第一个 loader 的传入参数只有一个:资源文件(resource file)的内容。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String
或者 Buffer
(被转换为一个 string),代表了模块的 JavaScript 源码。另外还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。
如果是单个处理结果,可以在同步模式中直接返回。如果有多个处理结果,则必须调用 this.callback()
。在异步模式中,必须调用 this.async()
,来指示 loader runner 等待异步结果,它会返回 this.callback()
回调函数,随后 loader 必须返回 undefined
并且调用该回调函数。
最简单的Loader
同步 loader
无论是 return
还是 this.callback
都可以同步地返回转换后的 content
内容:
sync-loader.js
1 | module.exports = function(content, map, meta) { |
this.callback
方法则更灵活,因为它允许传递多个参数,而不仅仅是content
。
这里的content就是传入的源码。someSyncOperation是我们自定义的方法用于处理源码,最终将处理结果通过return返回。
sync-loader-with-multiple-results.js
1 | module.exports = function(content, map, meta) { |
这里的map其实是scourceMap。
异步 loader
对于异步 loader,使用 this.async
来获取 callback
函数:
async-loader.js
1 | module.exports = function(content, map, meta) { |
async-loader-with-multiple-results.js
1 | module.exports = function(content, map, meta) { |
this.aysnc 告诉 loader-runner 这个 loader 将会异步地回调。返回 this.callback
。所以其参数与this.callback是相同的。
Loader Context 常用的方法及参数
这里只对常用的方法及参数进行列举,解释以及加一些注意事项。更多的配置可以直接去看文档
loader context 表示在 loader 内使用 this
可以访问的一些方法或属性。
假设我们这样请求加载别的模块: 在 /abc/file.js
中:
1 | require('./loader1?xyz!loader2!./resource?rrr'); |
上面这种引入方式是webpack提供的一种方法,这种引入方式指定了通过什么loader去加载最后的文件,loader是感叹号之前规定的,这种方法可以覆盖webpack.config中的配置。
具体可以看loader 概念文档
this.query`
- 如果这个 loader 配置了
options
对象的话,this.query
就指向这个 option 对象。 - 如果 loader 中没有
options
,而是以 query 字符串作为参数调用时,this.query
就是一个以?
开头的字符串。
在官方文档中提示我们最好使用loader-utils提供的getOptions方法去获取options,是因为如果options中的值不是单纯的string时,偶尔会有一些问题,具体的使用方法是:
1 | //webpack config |
1 | // loader.js |
this.callback
一个可以同步或者异步调用的可以返回多个结果的函数。预期的参数是:
1 | this.callback( |
- 第一个参数必须是
Error
或者null
- 第二个参数是一个
string
或者Buffer
。 - 可选的:第三个参数必须是一个可以被这个模块解析的 source map。
- 可选的:第四个选项,会被 webpack 忽略,可以是任何东西(例如一些元数据)。
可以将抽象语法树(abstract syntax tree - AST)(例如
ESTree
)作为第四个参数(meta
),如果你想在多个 loader 之间共享通用的 AST,这样做有助于加速编译时间。
如果这个函数被调用的话,你应该返回 undefined 从而避免含糊的 loader 结果。
this.async
告诉 loader-runner 这个 loader 将会异步地回调。返回 this.callback
。
this.version
**loader API 的版本号。**目前是 2
。这对于向后兼容性有一些用处。通过这个版本号,你可以为不同版本间的破坏性变更编写不同的逻辑,或做降级处理。
this.context
**模块所在的目录。**可以用作解析其他模块路径的上下文。
在我们的例子中:这个属性为 /abc
,因为 resource.js
在这个目录中
this.rootContext
从 webpack 4 开始,原先的 this.options.context
被改进为 this.rootContext
。
this.request
被解析出来的 request 字符串。
在我们的例子中:
"/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr"
这个结果是怎么来的呢?
首先因为我们的resource.js,loader1,loader2的根目录abc目录中,所以前面会添加"abc/",而loader2前面没有相对路径,所以认为是node_modules中的模块,就会再补上一个”node_modules/“
高级的Loader
“Raw” loader
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw
,loader 可以接收原始的 Buffer
。每一个 loader 都可以用 String
或者 Buffer
的形式传递它的处理结果。Complier 将会把它们在 loader 之间相互转换。
raw-loader.js
1 | module.exports = function(content) { |
越过 loader(Pitching loader)
loader 总是从右到左地被调用。有些情况下,loader 只关心 request 后面的元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先从左到右调用 loader 上的 pitch
方法。对于以下 use
配置:
1 | module.exports = { |
将会发生这些步骤:
1 | |- a-loader `pitch` |
那么,为什么 loader 可以利用 “跳跃(pitching)” 阶段呢?
首先,传递给 pitch
方法的 data
,在执行阶段也会暴露在 this.data
之下,并且可以用于在循环时,捕获和共享前面的信息。
1 | module.exports = function(content) { |
其次,如果某个 loader 在 pitch
方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果 b-loader
的 pitch
方法返回了一些东西:
1 | module.exports = function(content) { |
上面的步骤将被缩短为:
1 | |- a-loader `pitch` |