# 编写一个loader

定义: loader 只是一个导出为函数的 JavaScript 模块。

一个简单的loader即可以通过编写一个简单的JavaScript模块,并将模块导出即可。即如下, 接受一个source当前源码,并返回新的源码即可。

module.exports = function(source) {
  return source
}

# loader的执行顺序:由后向前

其中,多个loader的执行顺序是由后向前执行,例如:

module.exports = {
    module: {
        rules:[{
            test:/.less$/,
            use: [
                'style-loader',
                'css-loader',
                'less-loader'
            ]
        }]
    }
}

运行代码:https://github.com/PCAaron/blogCode/tree/master/webpack/loader-order
loader运行顺序

# loader的开发和调试:loader-runner

loader-runner运行在不安装webpack的情况下运行loaders,进行loader的开发和调试。其中,在webpack中, 作为webpack的依赖,webpack使用它执行loader。

  • loader-runner的使用:
import { runLoaders } from 'loader-runner'
runLoaders({
    // String: 资源的绝对路径(可以增加查询字符串)
    resource: "/abs/path/to/file.txt?query", 
    // String[]: loader 的绝对路径(可以增加查询字符串)
    loaders: ["/abs/path/to/loader.js?query"],
    // 基础上下文之外的额外 loader 上下文
    context: { minimize: true }, 
    // 读取资源的函数
    readResource: fs.readFile.bind(fs) 
}, function(err, result) {
    // err: Error?
    // result.result: Buffer | String
    // result.resourceBuffer: Buffer
    // result.cacheable: Bool 结果是否需要缓存
    // result.fileDependencies: String[] 解析结果需要依赖的文件
    // result.contextDependencies: String[] 上下文依赖
})
  • 调试raw-loader
    实现开发一个loader,将文件内容转换为模块:处理特殊字符转义并以模块方式导出。
//src/raw-loader
module.exports = function (source) {
    const json = JSON.stringify(source)
    json.replace(/\u2028/g,'\\u2028')
        .replace(/\u2029/g,'\\u2029')
    return `export default ${json}`
}

新建run-loader.js调试raw-loader。

//run-loader.js
const fs = require('fs')
const path = require('path')
const { runLoaders } = require('loader-runner')

runLoaders({
    resource: path.join(__dirname, './src/demo.txt'),
    loaders: [
        path.join(__dirname, './src/raw-loader.js')
    ],
    context: { minimize: true },
    readResource: fs.readFile.bind(fs)
},(err, result) => {
    err ? console.error(err) : console.log(result)
})

线上代码:https://github.com/PCAaron/blogCode/tree/master/webpack/raw-loader 开发调试loader

# loader-utils:loader的参数获取

通过loader-utils的getOptions方法可以获取到loader传递过来的参数。

//run-loader.js
runLoaders({
    loaders: [
        {
            loader: path.join(__dirname, './src/raw-loader.js'),
            options: {
                name: 'test'
            }
        }
    ],
    ...
})
//src/raw-loader.js
const loaderUtils = require('loader-utils')
module.exports = function (source) {
    const { name } = loaderUtils.getOptions(this)
    console.log(name)
}

# loader API

loader-runner提供一些API给给定函数调用。

# loader的同步处理

实现同步的loader的异常处理:

  • 可通过在loader内直接抛出错误throw new Error(err);
  • 亦可以通过loader上下文的回调函数this.callback传递错误并返回回调内容
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap, // 必须是一个可以被这个模块解析的 source map
  meta?: any // 会被 webpack 忽略,可以是任何东西(例如一些元数据)
);
//src/raw-loader.js
module.exports = function (source) {
    const json = JSON.stringify(source)
    ...
    //return `export default ${json}`
    // throw new Error('err')
    this.callback(null, json)
}

# loader的异步处理

通过loader上下文函数this.async来返回一个异步函数,其中,第一个参数为Error,第二个参数为回调内容。

// src/raw-loader.js
const path = require('path')
const fs = require('fs')

module.exports = function (source) {
    // 异步方式
    const callback = this.async()
    ...
    fs.readFile(path.join(__dirname, './async.txt'), 'utf-8', (err, data) => {
        if(err) {
            callback(err, '')
            return
        }
        callback(null, data)
    })
}

# loader中使用缓存

在webpack中,会默认开启loader缓存,如果不需要缓存则可以通过this.cacheable(false)关闭。
另外,缓存使用的条件需要在,loader的结果在相同的输入下有确定的输出,并且在有依赖的loader下是 无法使用缓存的。

# this.emitFile:文件输出

通过loader上下文函数this.emitFile可以将内容输出到指定目录。

# 推荐阅读

编写一个loader:https://www.webpackjs.com/contribute/writing-a-loader/