webpack plugin

150 阅读5分钟

什么是webpack plugin

  1. 官方解释:

    插件是 webpack 生态的关键部分, 它为社区用户提供了一种强有力的方式来直接触及 webpack 的编译过程
    (compilation process)。插件能够到每一个编译(compilation)发出的关键事件中。 在编译的每个阶段中,插件都拥,
    有对 `compiler` 对象的完全访问能力, 并且在合适的时机,还可以访问当前的 `compilation` 对象
    
  2. 理解: webpack 在整个打包的流程上,暴露了很多的事件,我们可以通过监听这些事件,实现一些我们自定义的功能,比如 打包输出前清空dist目录CleanWebpackPlugin。使用html模板,并引用打包后的js 文件HtmlWebpackPlugin

  3. 所以我们要想实现一个webpack plugin, 需要了解一些概念

  • webpack的构建流程, 以便于我们在编写plugin时,在合适的时间,监听事件并做相关的处理
  • webpack 提供了哪儿些钩子函数让我们便于监听和操作? # compiler.hookscompilation.hooks
  • compiler 和 compilation 是什么

# compiler 和 compilation 是什么?

  1. compiler: 构建之初就已经创建,并且贯穿webpack整个生命周期[ before - run - beforeCompiler - complie - make - finishMake - afterComplie - done ],即每次运行webpack(或运行 npm run serve 和 npm run build 等) 构建时实例,且只有一个。

  2. compilation: compilation是到准备编译模块时,才会创建compilation对象 是 compile - make 阶段主要使用的对象。

# compiler.hooks

  1. 实现继承自tapable

    compile 的 hooks 执行顺序是按照webpack 官网所列举,右上到下开始执行的。具体每个钩子的含义及使用,需自己测试和阅读理解

  2. 它有以下主要属性:

  • compiler.options 可以访问本次启动webpack 时候所有的配置文件,包括但不限于 loaders、entry、output、plugin 等等

  • compiler. inputF1lesystem 和 compiler.outputFilesystea 可以进行文件操作,相当于 Nodejs 中的 fs

  • compiler.hooks 可以注册 tapable 的不同种类 Hook,从而可以在compiler 生命周期中植入不同的逻辑。

    compile.hooks查看

compilation.hooks

  1. 实现继承自tapable

    compilation 的 hooks 的执行顺序是按照webpack 官网所列,右上到下开始执行的。

    compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。

    一个compilation 对家会对构建依赖图中所有模块进行编译, 在编译阶段,横块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

  2. 它有以下主耍属性:

  • compilation.modules 可以访问所有横块,打包的每一个文件都是一个模块。

  • compilation.chunks chunk 即是多个modules 组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。

  • compilation.assets 可以访问本次打包生成所有文件的结果。

  • compilation.hooks 可以注册 tapable 的不同种类 Hook,用于在compilation 编译模块阶段进行逻辑添加以及修改。

    compilation.hooks 查看

tapable 中的hooks

tapable 是 webpack 的一个核心工具,但也可用于其他地方, 以提供类似的插件接口。 在 webpack 中的许多对象都扩展自 Tapable 类。 它对外暴露了 taptapAsync 和 tapPromise 等方法, 插件可以使用这些方法向 webpack 中注入自定义构建的步骤,这些步骤将在构建过程中触发。

compile中对tapable 的引用如下

const {
	SyncHook,
	SyncBailHook,
	AsyncParallelHook, 
	AsyncSeriesHook
} = require("tapable");

compilation中对tapable 的引用如下

const {
	HookMap,
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncParallelHook
} = require("tapable");

当使用 syncHook 声明的钩子,则监听时使用 tap, 如: initialize compile 等

源码中的声明:
    this.hooks = {
        compile: new SyncHook(["params"]), // params 是回调接收的参数
        run: new AsyncSeriesHook(["compiler"]), // compiler 是回调接收的参数
        ...
    }

同步监听使用`tap`:
compiler.hooks.compile.tap('MyPlugin', (params) => {
  console.log('以同步方式触及 compile 钩子。');
});

异步监听则可使用 tapAsynctapPromisetap

  • tapAsync: 多一个参数 callback
  • tapPromise: return 一个promise
compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
    console.log('以异步方式触及运行钩子。');
    setTimeout(() => {
      callback()
    }, 2000);
  }
);

compiler.hooks.run.tapPromise('MyPlugin', (compiler) => {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      console.log('异步任务完成...');
      resolve();
    }, 1000);
  });
});

所以当我们编写插件进行监听时,要记得对应使用 tap tapAsync tapPromise

注意:当我们注册compilation 的钩子函数时,至少要在 make 阶段。如果是在 afterCompile之后注册,是不会执行的

关于各个钩子函数的意义,可百度自行搜索下。如:tapable1

webpack 的构建流程

image.png

参考链接:

plugin打包流程参考链接

plugin打包流程参考链接

编写插件的方法

我们在webpack中使用方式如下

const myPlugin = require('./pliugins/myPlugin')

module.exports = {
  plugins: [
    new myPlugin({
      filename: 'myPlugin.md', // 传递的对象为constructor 中的 option
    })
  ]
};

自定义编写一个插件 myPlugin.js

  • 通过module.export 导出一个类
  • 整个类必须有个函数 即 apply, 参数是compile
  • apply 内部就可以监听相关的参数是compile.hooks事件去做自定义的处理
class myPlugin {
  constructor (options) {
    // 获取插件配置项
    this.filename = options && options.filename ? options.filename : 'FILELIST.md';
  }
  apply(compiler) {
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
      // 通过 compilation.assets 获取文件数量
      let len = Object.keys(compilation.assets).length;

      // 添加统计信息
      let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpack\n\n`;

      // 通过 compilation.assets 获取文件名列表
      for (let filename in compilation.assets) {
        content += `- ${filename}\n`;
      }

      // 往 compilation.assets 中添加清单文件
      compilation.assets[this.filename] = {
        // 写入新文件的内容
        source: function () {
          return content;
        },
        // 新文件大小(给 webapck 输出展示用)
        size: function () {
          return content.length;
        }
      }

      // 执行回调,让 webpack 继续执行
      cb();
    })
  }
}

module.exports = myPlugin

结果: 生成一个 filelist.md 的统计信息

image.png

注意:

  1. plugin 不建议更改 源文件的内容信息,因为loader 本身可以帮我们实现类似的功能。
  2. 如果使用plugin 更改源文件的内容,官方建议是在 complition.hooks.seal 之前。

使用node 进行调试【非常有用奥】

  1. 首先在我们的开发环境中添加一个npm 命令 debug

    "scripts": {
        "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
      },
    
  2. 在我们开发的插件中添加一个debug

  3. 运行命令 npm run debug 进入debug 状态 image.png

  4. 打开浏览器随便的一个tab页面,并打开控制台 image.png

  5. 在点击【绿色六边形】后即可进入调试界面。

  6. 然后即可进入调试,查看compiler 和 compilation。操作其属性进行plugin的编写