如何编写一个webpack loader

最近一套代码多端运行的设计理念在整个前端界有点过火了,当然这种多端运行的设计都离不开各种加载打包工具。将一套代码转换成各种端能够识别代码,就需要使用各种loader。本文主要通过一个简单的同步例子指导如何编写一个webpack loader。
关于如何编写一个webpack loader,webpack官方同样提供相关的技术文档writing-a-loader
注意:本文使用的同步例子比较简单,并没有完全覆盖技术文档的知识点。

概念

官方给出的概念如下:

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。

在编写一个loader模块的时候,还需要注意以下几点:

  • loader 可以被链式调用意味着不一定要输出 JavaScript。只要下一个 loader 可以处理这个输出,这个 loader 就可以返回任意类型的模块。
  • 当链式调用多个 loader 的时候,请记住它们会以相反的顺序执行。取决于数组写法格式,从右向左或者从下向上执行。
    • 最后的 loader 最早调用,将会传入原始资源内容。
    • 第一个 loader 最后调用,期望值是传出 JavaScript 和 source map(可选)。
    • 中间的 loader 执行时,会传入前一个 loader 传出的结果。

示例项目

项目结构

打开本示例在Github上的地址,即可查看整个示例项目结构.

需求

编写一个webpack loader将public/index.html文件中的{{__content__}}局部代码转换成public/layout.html中的内容。
index.html中的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  {{__content__}}
</body>
</html>

layout.html中的代码如下:

<div>
  ruanrongcheng
</div>

index.js入口文件

使用webpack转换资源就需要有一个入口文件,存放于src/index.js。index.js入口文件中的代码如下:

const tpl = require('../public/index.html')
console.log(tpl)

代码很简单,打印出结果,即可验证{{__content__}}是否替换成功

webpack.config.js

const path = require('path')
module.exports = {
  name: 'test',
  target: 'web',
  entry: './src/index.js',
  mode: 'development',
  output: {
    path: path.resolve(__dirname, './'),
    filename: 'test.js'
  },
  module: {
    rules: [
      { parser: { requireEnsure: false }},
      {
        oneOf: [{
          test: /\.(html)$/,
          use: [
            {
              loader: require.resolve('html-loader')
            },
            {
              loader: path.resolve(__dirname, './layout-loader'),
              options: {
                layout: path.resolve(__dirname, './public/layout.html')
              }
            }
          ]
        }]
      }
    ]
  }
}

在处理html类型资源文件的时候,必须同时优先使用自定义的layout-loader来处理html文件,然后将处理后的字符串或者buffer传给下一个loader。

layout-loader

layout-loader文件夹中的index.js内容如下:

const loaderUtils = require('loader-utils')
const fs = require('fs')
const defaultOptions = {
  placeholder: '{{__content__}}'
}
module.exports = function(content) {
  const loaderContext = this
  const options = Object.assign(loaderUtils.getOptions(loaderContext), defaultOptions)
  const layoutHtml = fs.readFileSync(options.layout, 'utf-8')
  return content.replace(defaultOptions.placeholder, layoutHtml)
}

这里使用loader-utils模块来获取webpack.config.js中options的layout属性。
然后通过fs.readFileSync读取layout.html的内容

总结

本文使用例子偏简单,仅仅适合入门,还需要考虑到异步、缓存等情况,这些知识点请参考官方文档