loader-runner
Webpack 开箱即用只支持 JS 和 JSON 两种文件类型,通过 Loaders 去支持其它文件类型并且把它们转化成有效的模块(解析器),并且可以添加到依赖图中。
本身是一个函数,接受源文件作为参数,返回转换的结果。
loader 可以是异步的,也可以是同步的,同步的则直接返回处理后的模块内容,异步则调用异步回调函数输出处理后的模块内容。
loader 基础
loader 本质是一个函数,通过接受处理的函数,然后返回处理后的结果。
module.exports = function (content, sourcemap) {
// 处理 content 操作。..
return content
}
示例最后通过 return 返回了 loader 处理后的数据。但其实这并不是推荐写法,在大多数情况下,推荐使用 this.callback
方法去返回数据。
module.exports = function (content, sourcemap) {
// 处理 content 操作。..
this.callback(null, content)
}
this.callback
可以传入四个参数(其中后两个参数可以省略),分别是:
error
:当 loader 出错的时向外抛出一个Error
对象,成功则传入null
content
:经过 loader 编译后需要导出的内容,类型可以是String
或者Buffer
sourceMap
:为方便调试编译后生成的内容的 source mapast
:本次编译后生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,可以省去重复生成的 AST 的过程。
Tips:在编写 loader 的时候,如果要使用 this.callback 或者 loader-utils 的 getOptions 等方法,this 是 webpack 调用 loader 时候传入的自定义特殊上下文,所以不能使用箭头函数。
Loader 处理异步数据
Loader 中提供了两种异步写法:
async
/await
异步函数写法this.async
获取一个异步的callback
,然后返回它。
**async**
/**await**
异步函数写法
module.exports = async function (content) {
function timeout(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟一些异步操作处理 content
resolve(content)
}, delay)
})
}
const data = await timeout(1000)
return data
}
**this.async**
获取一个异步的 **callback**
module.exports = function (content) {
function timeout(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟一些异步操作处理 content
resolve(content)
}, delay)
})
}
const callback = this.async()
timeout(1000).then((data) => {
callback(null, data)
})
}
处理二进制数据
像 file-loader
这样的 Loader,实际处理的内容是二进制数据,那么就需要通过设置 module.exports.raw = true
来告诉 Webpack 给 loader 传入二进制格式的数据。
module.exports = function (source) {
if (source instanceof Buffer) {
// 一系列操作
Return source // 本身也可以返回二进制数据提供给下一个 loader
}
}
moudle.exports.raw = true // 不设置,就会拿到字符串
执行顺序 - pitch
Loader 的执行顺序是从右到左的链式调用。在一些场景下,loader 并不依赖上一个 loader 的结果,而只关心原输入内容。这时候,要拿到一开始的文件原内容,就需要使用 module.exports.pitch = function() {}
,pitch
方法在 loader 中便是从左到右执行的,并且可以通过 data 这个变量来进行 pitch 和 normal 之间的传递。
module.exports.pitch = function (remaining, preceding, data) {
if (somothingFlag()) {
return 'module.exports = require(' + JSON.stringify('-!' + remaining) + ');'
}
data.value = 1
}
pitch
可以接受三个参数,最重要的就是第三个参数,你可以为其挂载一些所需的值,一个 rule 里的所有 loader 在执行时都能拿到这个值。
module.exports = function (content) {
// this.data test
console.log('this data', this.data.value)
return content
}
module.exports.pitch = (remaining, preceding, data) => {
data.value = 'test'
}
loader 的结果缓存
Webpack 增量编译机制会观察每次编译时的变更文件,在默认情况下,Webpack 会对 loader 的执行结果进行缓存,这样能够大幅度提升构建速度,不过我们也可以手动关闭它。
module.exports = function (content) {
// 关闭 loader 缓存
this.cacheable(false)
return content
}
使用 Webpack 的工具库
在 webpack.config.js
书写 loader 配置时,经常会见到 options
这样一个配置项,或者在写内联调用 loader 的时候会通过 querystring
的形式传入 options,这是 Webpack 为 loader 用户提供的自定义配置。在 loader 里,可以拿到这些自定义配置。为了方便编写 loader,Webpack 官方将编写 loader 中常用的工具函数打包成了 loader-utils
和 schema-utils
模块,这里面包括了常用的获取 loader 选项(options)和参数验证等方法。
loader-utils
工具库
loader-utils
提供了各种跟 loader 选项(options)相关的工具函数
const { getOptions, stringifyRequest, parseQuery } = require('loader-utils')
module.exports = function (content) {
// getOptions 用于在 loader 里获取传入的 options,返回的是对象值。
const options = getOptions(this)
// stringifyRequest 转换路径,避免 require() 或 impot 时使用的绝对路径
stringifyRequest(this, './test.js') // Result => "\\"./test.js\\""
// parseQuery 获取 query 参数的,这个很简单就不说啦
parseQuery('?name=kev&age=14') // Result => {name: 'kev', age: '14'}
}
schema-utils
工具库
schema-utils
是 loader 和 plugin 的参数认证器,检测传入的参数是否符合预期。
const validateOptions = require('schema-utils')
// 下面是一个 schema 描述
const schema = {
type: 'object',
properties: {
name: {
type: 'string'
},
test: {
anyOf: [{ type: 'array' }, { type: 'string' }, { instanceof: 'RegExp' }]
},
transform: {
instanceof: 'Function'
},
sourceMap: {
type: 'boolean'
}
},
additionalProperties: false
}
module.exports = function (source) {
// 验证参数的类型是否正确。
validateOptions(schema, options, 'loader name')
}
loader 中 this 相关的其它方法和属性
this.context
:当前处理转换的文件所在的目录this.resource
:当前处理转换的文件完整请求路径,包括 querystringthis.resourcePath
:当前处理转换的文件的路径this.resourceQuery
:当前处理文件的 querystringthis.target
:Webpack 配置的 targetthis.loadMoudle
:处理文件时,需要依赖其它文件的处理结果时,可以使用this.loadMoudle(request: string, callback: function(err, source, sourceMap, module))
去获取到依赖文件的处理结果this.resolve
:获取指定文件的完整路径this.addDependency
:为当前处理文件添加依赖文件,以便依赖文件发生变化时重新调用 Loader 转换该文件,this.addDependency(file: string)
this.addContextDependency
:为当前处理文件添加依赖文件目录,以便依赖文件目录里文件发生变化时重新调用 Loader 转换该文件,this.addContextDependency(dir: string)
this.clearDependencies
:清除当前正在处理文件的所有依赖this.emitFile
:输出一个文件,使用的方法为this.emitFile(name: string, content: Buffer | string, sourceMap: {...})
this.emitError
:发送一个错误信息