从i18n国际化来学习如何创建Webpack插件

1,785 阅读3分钟

插件向第三方开放了 Webpack 引擎中完整的能力,它比 loader 能做更多的事情,这些构建的回调完整的构成了一个 Webpack plugin 的存在,因此我们需要理解一下 Webpack 底层内部的特性来做相应的钩子,这是一件很有趣的事情,学习完我们将用插件来解决一个实际性的问题。

那么一个 Webpack 插件有哪些方面组成:

  • 一个命名函数
  • 在函数的 prototype 上定义一个 apply 方法
  • 指定一个 webpack hook
  • 处理 Webpack 内部实例特定的数据
  • 完成后调用 Webpack 提供的回调

这是我们开发的插件要经历过的固定顺序。在此之前,我们还需要稍微理解一下 CompilerCompilation,因为apple 会被 Webpack Compiler 调用,并且 compiler 对象会贯穿整个编译生命周期;

  • compiler 对象代表了完整的 Webpack 环境配置,这个对象在启动时被 Webpack 一次性创建,并且配置好可操作的配置,如:loaderoptions 等。
  • compilation 对象代表了资源版本构建,它可以理解为每当 webpack 构建启动时,检测到一个文件的变化,就会创建一个 compilation ,从而生成了一组可编译的资源。

说了这么多其实创建一个 Webpack plugin 很简单,只要你能按照上述的顺序创建一个函数,并且理解它的 Compiler ,我们能很快的实现一个插件。

案例

前一段时间在做 Chrome Extension 的国际化时就遇到了一些开发体验上的问题,我们知道 Chrome Extension 的国际化是在 _locales 中创建多个语种的目录和文件,它非常的分散,并且不利于维护,在做的过程中,就想到了 Webpack plugin 来解决这个问题,在预期的设计中,我希望国际化的配置文件能统一维护和处理,它的格式可能是这样的:

// i18n.json

{
  "README": {
    "zh_CN": "中文描述",
    "en": "README"
  }
}

转换的目录格式:

|- _locales
        |- en
            | messages.json
        |- zh_CN
            | messages.json

en/messages.json:

{
  "README": {
    "message": "README"
  }
}

zh_CN/messages.json:

{
  "README": {
    "message": "中文描述"
  }
}

它在使用的过程中,可能需要传递两个参数,如:filei18ns,这两个参数用于指定文件(这只是设计问题和本身插件的实现无关)。

new chromeExtensionI18nPlugin({
  i18ns: ['en', 'zh_CN'],
  file: './i18n.json'
})

接下来我们来创建一个 ChromeExtensionI18nPlugin 类,并且实现 apply,在 apply 方法中再去处理 Webpack Hook :

const I18N_OUTPUT_DIR = '_locales';

class ChromeExtensionI18nPlugin { 
  constructor(options) {
    this.options = extend({
      file: '',
      i18ns: [],
      spaceNum: 4,
    }, options);
  }

  apply(compiler) {
    ... // 处理完各种错误
    // 使用 compiler.hook 来异步处理
    compiler.hooks.emit.tapAsync('ChromeExtensionI18nPlugin', (compilation, callback) => {
      // node.js fs 模块
      readFileAsync(file, 'utf8').then((source) => {
        // 处理数据格式
        ...
        return result;
      }).then((result) => {
        // 每一个 compilation 代表了一次的资源构建
        // 处理构建资源
        ...
        compilation.assets[`${I18N_OUTPUT_DIR}/${i18n}/messages.json`] = {
          source() {
            return format(result[i18n], spaceNum);
          },
          size() {
            return format(result[i18n], spaceNum).length;
          }
        }
        callback();
      })
    }
  }
}

结语

用一个国际化简单的例子来展示如何创建一个 webpack plugin ,这是一个很有用案例,也解决了我的实际问题,虽然它的构造很简单,当我们比较理解 webpack 的实际结构时,我们可以利用插件来处理更复杂的问题。