loader-runner

image.png

Webpack 开箱即用只支持 JS 和 JSON 两种文件类型,通过 Loaders 去支持其它文件类型并且把它们转化成有效的模块(解析器),并且可以添加到依赖图中。

本身是一个函数,接受源文件作为参数,返回转换的结果。

loader 可以是异步的,也可以是同步的,同步的则直接返回处理后的模块内容,异步则调用异步回调函数输出处理后的模块内容。

loader 基础

loader 本质是一个函数,通过接受处理的函数,然后返回处理后的结果。

  1. module.exports = function (content, sourcemap) {
  2. // 处理 content 操作。..
  3. return content
  4. }

示例最后通过 return 返回了 loader 处理后的数据。但其实这并不是推荐写法,在大多数情况下,推荐使用 this.callback 方法去返回数据。

  1. module.exports = function (content, sourcemap) {
  2. // 处理 content 操作。..
  3. this.callback(null, content)
  4. }

this.callback 可以传入四个参数(其中后两个参数可以省略),分别是:

  • error:当 loader 出错的时向外抛出一个 Error 对象,成功则传入 null
  • content:经过 loader 编译后需要导出的内容,类型可以是 String 或者 Buffer
  • sourceMap:为方便调试编译后生成的内容的 source map
  • ast:本次编译后生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,可以省去重复生成的 AST 的过程。

Tips:在编写 loader 的时候,如果要使用 this.callback 或者 loader-utils 的 getOptions 等方法,this 是 webpack 调用 loader 时候传入的自定义特殊上下文,所以不能使用箭头函数。

Loader 处理异步数据

Loader 中提供了两种异步写法:

  • async/await 异步函数写法
  • this.async 获取一个异步的 callback,然后返回它。

**async**/**await** 异步函数写法

  1. module.exports = async function (content) {
  2. function timeout(delay) {
  3. return new Promise((resolve, reject) => {
  4. setTimeout(() => {
  5. // 模拟一些异步操作处理 content
  6. resolve(content)
  7. }, delay)
  8. })
  9. }
  10. const data = await timeout(1000)
  11. return data
  12. }

**this.async** 获取一个异步的 **callback**

  1. module.exports = function (content) {
  2. function timeout(delay) {
  3. return new Promise((resolve, reject) => {
  4. setTimeout(() => {
  5. // 模拟一些异步操作处理 content
  6. resolve(content)
  7. }, delay)
  8. })
  9. }
  10. const callback = this.async()
  11. timeout(1000).then((data) => {
  12. callback(null, data)
  13. })
  14. }

处理二进制数据

file-loader 这样的 Loader,实际处理的内容是二进制数据,那么就需要通过设置 module.exports.raw = true 来告诉 Webpack 给 loader 传入二进制格式的数据。

  1. module.exports = function (source) {
  2. if (source instanceof Buffer) {
  3. // 一系列操作
  4. Return source // 本身也可以返回二进制数据提供给下一个 loader
  5. }
  6. }
  7. moudle.exports.raw = true // 不设置,就会拿到字符串

执行顺序 - pitch

Loader 的执行顺序是从右到左的链式调用。在一些场景下,loader 并不依赖上一个 loader 的结果,而只关心原输入内容。这时候,要拿到一开始的文件原内容,就需要使用 module.exports.pitch = function() {}pitch 方法在 loader 中便是从左到右执行的,并且可以通过 data 这个变量来进行 pitch 和 normal 之间的传递。

  1. module.exports.pitch = function (remaining, preceding, data) {
  2. if (somothingFlag()) {
  3. return 'module.exports = require(' + JSON.stringify('-!' + remaining) + ');'
  4. }
  5. data.value = 1
  6. }

pitch 可以接受三个参数,最重要的就是第三个参数,你可以为其挂载一些所需的值,一个 rule 里的所有 loader 在执行时都能拿到这个值。

  1. module.exports = function (content) {
  2. // this.data test
  3. console.log('this data', this.data.value)
  4. return content
  5. }
  6. module.exports.pitch = (remaining, preceding, data) => {
  7. data.value = 'test'
  8. }

loader 的结果缓存

Webpack 增量编译机制会观察每次编译时的变更文件,在默认情况下,Webpack 会对 loader 的执行结果进行缓存,这样能够大幅度提升构建速度,不过我们也可以手动关闭它。

  1. module.exports = function (content) {
  2. // 关闭 loader 缓存
  3. this.cacheable(false)
  4. return content
  5. }

使用 Webpack 的工具库

webpack.config.js 书写 loader 配置时,经常会见到 options 这样一个配置项,或者在写内联调用 loader 的时候会通过 querystring 的形式传入 options,这是 Webpack 为 loader 用户提供的自定义配置。在 loader 里,可以拿到这些自定义配置。为了方便编写 loader,Webpack 官方将编写 loader 中常用的工具函数打包成了 loader-utilsschema-utils 模块,这里面包括了常用的获取 loader 选项(options)和参数验证等方法。

loader-utils 工具库

loader-utils 提供了各种跟 loader 选项(options)相关的工具函数

  1. const { getOptions, stringifyRequest, parseQuery } = require('loader-utils')
  2. module.exports = function (content) {
  3. // getOptions 用于在 loader 里获取传入的 options,返回的是对象值。
  4. const options = getOptions(this)
  5. // stringifyRequest 转换路径,避免 require() 或 impot 时使用的绝对路径
  6. stringifyRequest(this, './test.js') // Result => "\\"./test.js\\""
  7. // parseQuery 获取 query 参数的,这个很简单就不说啦
  8. parseQuery('?name=kev&age=14') // Result => {name: 'kev', age: '14'}
  9. }

schema-utils 工具库

schema-utils 是 loader 和 plugin 的参数认证器,检测传入的参数是否符合预期。

  1. const validateOptions = require('schema-utils')
  2. // 下面是一个 schema 描述
  3. const schema = {
  4. type: 'object',
  5. properties: {
  6. name: {
  7. type: 'string'
  8. },
  9. test: {
  10. anyOf: [{ type: 'array' }, { type: 'string' }, { instanceof: 'RegExp' }]
  11. },
  12. transform: {
  13. instanceof: 'Function'
  14. },
  15. sourceMap: {
  16. type: 'boolean'
  17. }
  18. },
  19. additionalProperties: false
  20. }
  21. module.exports = function (source) {
  22. // 验证参数的类型是否正确。
  23. validateOptions(schema, options, 'loader name')
  24. }

loader 中 this 相关的其它方法和属性

  • this.context:当前处理转换的文件所在的目录
  • this.resource:当前处理转换的文件完整请求路径,包括 querystring
  • this.resourcePath:当前处理转换的文件的路径
  • this.resourceQuery:当前处理文件的 querystring
  • this.target:Webpack 配置的 target
  • this.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:发送一个错误信息

markdown-webpack-loader