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对象,成功则传入nullcontent:经过 loader 编译后需要导出的内容,类型可以是String或者BuffersourceMap:为方便调试编译后生成的内容的 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(() => {// 模拟一些异步操作处理 contentresolve(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(() => {// 模拟一些异步操作处理 contentresolve(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 testconsole.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:发送一个错误信息
