# 一.loader的实现
# 1.loader 是一个函数
先看一个简单的例子:
const marked = require("marked")
const laoderUtils = require("laoder-utils")
module.exports = function(markdown) {
// 使用loaderUtils来获取loader的配置项
// this 是构建运行时的一些上下文信息
const options = loaderUtils.getOptions(this)
this.cacheable()
// 把配置项直接传递给marked
marked.setOptions(options)
// 使用marked处理marked字符串,然后返回
return marked(markdown)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这是markdown-loader
的实现代码,笔者添加了一些代码说明,看上去很简单。
markdown-loader 本身仅仅只是一个函数,接收模块代码的内容,然后返回代码内容转化后的结果。webpack loader 的本质就是这样的一个函数。
上述代码中用到loader-utils
是 webpack 官方提供的一个工具库,提供 loader 处理是需要用到的一些工具方法,例如用来解析上下文 loader 配置项的getOptions
。
代码中还用到了marked
,marked 是一个用于解析 Markdown 的类库,可以把 Markedown 转为 HTML,markdown-loader 的核心功能就是用它实现的。基本上,webpack loader 都是基于一个实现核心功能的类库来开发的,例如sass-loader
是基于node-sass
实现的,等等。
# 2.开始一个 laoder 的开发
我们可以在 webpack 配置中直接使用路径来指定使用本地 loader,或者在 loader 路径解析中加入本地开发 loader 的目录。看看配置例子:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: path.resolve("./loader/index.js"), //使用本地的 ./loader/index.js作为loader
},
]
},
// 在resolveLoader中添加本地开发的loaders存放路径
// 如果你同时需要开发多个laoder,那么这个方式会更加适合你
resolveLoader:{
modules:[
'node_modules',
path.resolve(__dirname,'loaders')
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.复杂一点的情况
当我们选择上述任意一种方法,并且做好相应的准备后,我们就可以开始写 loader 的代码了,然后通过执行 webpack 构建来查看 loader 是否正常工作。
上面已经提到,loader 是一个函数,接收代码内容,然后返回处理结果,有一些 laoder 的实现基本上就是这么简单,但是有时候遇见相对复杂一点的情况。
首先 loader 函数接受的参数是有三个的:content, map, meta
。content
是模块内容,但不仅限于字符串,也可以是 buffer,例如一些图片或者字体等文件。map
则是 sourcemap 对象,meta
是其他的一些元数据。loader 函数单纯返回一个值,这个值是当成 content 去处理,但如果你需要返回 sourcemap 对象或者 meta 数据,甚至是抛出一个 laoder 异常给 webpack 是,你需要使用this.callback(err, content, map, meta)
来传递这些数据。
我们日常使用 webpack,有时候会把多个 laoder 串起来一起使用,最常见的莫过于 css-loader 和 style-loader 了。当我们皮遏制use:['bar-loader', 'foo-loader']
时,loader 是以相反的顺序执行的,即先跑 foo-loader,再跑 bar-loader。这一部分内容在配置 loader 的小节中有提及,这里再以开放 loader 的角度稍稍强调下,搬运官网的一段说明:
- 最后的 loader 最早调用,传入原始的资源内容(可能是代码,也可能是二进制文件,用 buffer 处理)
- 第一个 laoder 最后调用,期望返回时 JS 代码和 sourcemap 对象
- 中间 laoder 执行时,传入的是上一个 loader 执行的结果
虽然有多个 loader 时遵循这样的执行顺序,但对于大多数单个 laoder 来说无须感知这一点,只负责处理接受的内容就好。
还有一个场景是 loader 中的异步处理。有一些 loader 在执行过程中坑你依赖外部 I/O 的结果,导致他必须使用异步的方式来处理,这个使用需要咋 loader 是使用this.async()
来标识该 loader 是异步处理的,然后使用this.callback
来返回 laoder 处理结果。
# 4.Pitching loader
我们可以使用pitch
来跳过 loader 的处理,pitch
方法是 loader 额外实现的一个函数,看下官方文档中的一个例子:
module.exports = function(content) {
return someSyncOperation(content, this.data.value) //pitch的缘故,这里的data.value为42
}
// 挂在loader函数上的pitch函数
module.exports.pitch = function(remainingRequest, precedingRequest, data) {
data.value = 42
}
2
3
4
5
6
7
8
我们可以简单把pitch
理解为 loader 的前置钩子,它可以使用this.data
来传递数据,然后具备跳过剩余 loader 的能力。
在一个use
配置中所有 loader 执行前会先执行它们对应的pitch
,并且与 loader 执行顺序是相反的,如:
use: [
'bar-loader',
'foo-loader'
],
// 执行bar-loader的pitch
// 执行foo-loader的pitch
// bar-loader
// foo-loader
2
3
4
5
6
7
8
其中,当 pitch 中返回了结果,那么执行顺序会回过头来,跳掉剩余的 loader,如bar-laoder
的 pitch 返回结果了,那么执行剩下
// 执行bar-loader 的pitch
可能只有比较少的 loader 会用到 pitch 这个功能,但有的时候需要考虑实现 loader 功能需求时把 pitch 纳入范围会有不一样的灵感,它可以让你更加灵活地去定义 loader 的执行。
# 5.loader 上下文
上述提及的一些代码会使用到this
,即 loader 函数的上下文,包括this.callback
和this.data
等。可以这样简单地理解:this
是作为 laoder 运行时数据和调用犯法的补充载体。
laoder 上下文有很多运行时的信息,如this.context
和this.request
等等,而最重要的方法莫过于this.callback
和this.async
,关于上下文这里不做展开,官方文档有比较详细的说明。