返回

webpack实战——手写常用plugin

前端

webpack实战——手写常用plugin

引言

在上篇文章《webpack loader实战——手撕8个常用loader》中,我们主要介绍了 loader 的实现。loader 主要做的事情是针对某一类型的文件进行特定的处理,在 webpack 中,loader 是通过一定的规则来应用到不同的文件中的。而 plugin 则不同,plugin 可以对 webpack 的编译过程进行更深层次的介入,可以监听 webpack 的事件,并对 webpack 的编译结果进行修改。

plugin的实现

定义plugin

webpack 的 plugin 是通过实现 webpack.Plugin 接口来定义的,该接口只有一个方法 apply,该方法接受 compiler 作为参数,compiler 对象代表了 webpack 的编译器,我们可以通过 compiler 对象来监听 webpack 的事件,并对 webpack 的编译结果进行修改。

监听事件

我们可以通过 compiler 对象来监听 webpack 的各种事件,比如 beforeRun、run、compilation、done 等事件。当某个事件触发时,我们可以通过 compiler 对象来获取相关的信息,并做出相应的处理。

修改编译结果

我们可以通过 compiler 对象来修改 webpack 的编译结果,比如我们可以通过 compiler.assets 对象来获取编译后的文件,并对这些文件进行修改。

手写常用plugin

BannerPlugin

BannerPlugin 是一个非常常用的 plugin,它的作用是在每个编译后的文件中添加一个 banner。我们可以通过以下代码来实现这个 plugin:

class BannerPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap('BannerPlugin', compilation => {
      compilation.hooks.processAssets.tap(
        {
          name: 'BannerPlugin',
          stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
        },
        assets => {
          Object.keys(assets).forEach(assetName => {
            const asset = assets[assetName];
            if (asset.source && typeof asset.source === 'string') {
              asset.source = this.options.banner + '\n' + asset.source;
            }
          });
        },
      );
    });
  }
}

DefinePlugin

DefinePlugin 是另一个常用的 plugin,它的作用是在编译时定义一些全局变量。我们可以通过以下代码来实现这个 plugin:

class DefinePlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap('DefinePlugin', compilation => {
      compilation.hooks.optimizeChunks.tap('DefinePlugin', chunks => {
        chunks.forEach(chunk => {
          chunk.modules.forEach(module => {
            module.variables.forEach(variable => {
              if (variable.expression.type === 'Identifier') {
                if (variable.expression.name in this.options) {
                  variable.expression.type = 'Literal';
                  variable.expression.value = this.options[variable.expression.name];
                }
              }
            });
          });
        });
      });
    });
  }
}

HotModuleReplacementPlugin

HotModuleReplacementPlugin 是一个非常重要的 plugin,它的作用是在开发环境下实现热更新。我们可以通过以下代码来实现这个 plugin:

class HotModuleReplacementPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap('HotModuleReplacementPlugin', compilation => {
      compilation.hooks.afterCompile.tap('HotModuleReplacementPlugin', stats => {
        const json = JSON.stringify(stats.toJson());
        const script = `
          window.__webpack_hash__ = '${stats.hash}';
          window.__webpack_modules__ = ${json};
        `;
        compilation.assets['webpack-hot-middleware/client.js'] = {
          source: script,
          size: script.length,
        };
      });
    });
  }
}

其他plugin

除了以上几个 plugin 之外,webpack 中还有很多其他的 plugin,比如 CleanWebpackPlugin、CopyWebpackPlugin、ExtractTextPlugin 等。这些 plugin 可以帮助我们解决各种各样的问题,比如清理编译后的文件、拷贝文件、提取 CSS 等。

结语

webpack plugin 是一个非常强大的功能,我们可以通过它来扩展 webpack 的功能,满足我们的各种需求。本文只是介绍了一些最常用的 plugin,还有很多其他的 plugin 可以探索。我希望本文能帮助你了解 webpack plugin 的实现原理,并激发你开发出自己的 plugin。