【javascript】手写一个webpack plugin


手写一个plugin

webpack中的plugin这个概念

 插件是webpack的“支柱”功能,在项目中你肯定使用到了插件系统,比如:

html-webpack-plugin以及webpack内置的:HotModuleReplacementPluginDefinePlugin

 我们既然已经有loader了,为什么还需要插件呢,plugin是用来扩展webpack功能,他在构建流程里注入钩子来实现,为webpack带来了很大的灵活性。

 在webpack运行的声明周期中会广播许多事件,plugin可以监听这些事件,在特定的时刻调用webpack提供的API执行相应的操作。


当前的项目基于上面的write-loader-diy项目而来,里面有一些loader的配置,我们将这个文件放在plugin目录下面:

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  // 构造器参数,用于传递options
  constructor(options) {
    console.log("current plugin option is" + JSON.stringify(options))
  }
  // apply 方法是一个插件所必须的
  apply(compiler) {
    // compiler 继承自 tapable
    // tapable  提供了多种 hooks  https://github.com/webpack/tapable#hook-types
    // run      是 AsyncSeriesHook实例 [tapable提供的多种hooks的一种]
    compiler.hooks.run.tap(pluginName, compilation => {
      console.log('webpack 构建过程开始!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin

 webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

然后在webpack.config.js里面增加plugin的配置。

var ConsoleLogOnBuildWebpackPlugin = require("./plugin/ConsoleLogOnBuildWebpackPlugin")
// 期待代码略去
//...

module.exports = {
    plugins: [
        new ConsoleLogOnBuildWebpackPlugin({value: "$$"})
    ]
}

然后我们运行

npx webpack

就能在console中看到打印的日志信息了。

compiler

但是这个compiler是什么呢。。

这个compiler扩展自tapable类,这个类暴露了多种hooks钩子,这些钩子可以让我们在编写plugin的时候使用到;不仅如此, 它也包含了webpack的options,loaders,plugins等信息,全局唯一。

所以这一句:compiler.hooks.run.tapcompiler.hoos是拿到了所有的钩子,.run是去拿到了一个特定的钩子AsyncSeriesHook实例,从tapable的文档可以看出,这只是其中的一个。最后调用的tap方法是继承自interface Hook

源码看这里:

class Compiler extends Tapable {
	constructor(context) {
		super();
		this.hooks = {
			/** @type {SyncBailHook<Compilation>} */
			shouldEmit: new SyncBailHook(["compilation"]),
			/** @type {AsyncSeriesHook<Stats>} */
			done: new AsyncSeriesHook(["stats"]),
			/** @type {AsyncSeriesHook<>} */
			additionalPass: new AsyncSeriesHook([]),
			/** @type {AsyncSeriesHook<Compiler>} */
			beforeRun: new AsyncSeriesHook(["compiler"]),
			/** @type {AsyncSeriesHook<Compiler>} */
			run: new AsyncSeriesHook(["compiler"]),
			/** @type {AsyncSeriesHook<Compilation>} */
			emit: new AsyncSeriesHook(["compilation"]),
			/** @type {AsyncSeriesHook<Compilation>} */
			afterEmit: new AsyncSeriesHook(["compilation"])
			//....

我们上面的hooks.run实际上调用的是异步钩子,我们可以用其他异步的方式去将hook钩入到compiler实例上。

compiler.hooks.run.tapAsync('MyPlugin', (source, target, routesList, callback) => {
  console.log('以异步方式触及 run 钩子。');
  callback();
});

compiler.hooks.run.tapPromise('MyPlugin', (source, target, routesList) => {
  return new Promise(resolve => setTimeout(resolve, 10000)).then(() => {
    console.log('以具有延迟的异步方式触及 run 钩子。');
  });
});

compiler.hooks.run.tapPromise('MyPlugin', async (source, target, routesList) => {
  await new Promise(resolve => setTimeout(resolve, 10000));
  console.log('以具有延迟的异步方式触及 run 钩子。');
});

分别将这三种方式替换掉 run.tap然后查看效果吧!

compilation

同样的compilation钩子也是继承自Tapable,那么它也具有compiler的同样的方法和特性。Compilation 模块会被 Compiler 用来创建新的编译(或新的构建)。Compiler可以理解为整个webpack生命周期都存在的编译[构建]对象,但是Compliation只代表着某一次的编译[构建]对象。

compilation对象包含了当前的模块资源,编译生成资源,变化的文件等。在开发模式下,当检测到一个文件变化,就有一个compilation被创建。compilation对象也提供了很多回调供plugin进行扩展,也可以通过compilation获取到compiler对象。

我们来编写一个插件,来记录最终打包生成的文件列表:

class FileListPlugin {
  apply(compiler) {
    // emit 是异步 hook,使用 tapAsync 触及它,还可以使用 tapPromise/tap(同步)
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
      // 在生成文件中,创建一个头部字符串:
      var filelist = 'In this build:\n\n';

      // 遍历所有编译过的资源文件,
      // 对于每个文件名称,都添加一行内容。
      for (var filename in compilation.assets) {
        filelist += '- ' + filename + '\n';
      }

      // 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
      compilation.assets['filelist.md'] = {
        source: function() {
          return filelist;
        },
        size: function() {
          return filelist.length;
        }
      };

      callback();
    });
  }
}

module.exports = FileListPlugin;

advanced

自定义钩子函数


参考:
https://webpack.docschina.org/api/plugins/

https://webpack.docschina.org/api/compiler-hooks/#watchrun

项目地址:

https://github.com/aeolusheath/webpack-certain-features/tree/master/write-plugin-diy

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写一个webpack插件,你可以按照以下步骤进行操作: 1. 首先,了解webpack自身插件的调用流程。你可以参考webpack内部插件的实现方式来理解。其中一个例子是NodeEnvironmentPlugin插件。 2. 创建一个新的插件文件,比如TestPlugin.js,并在文件中定义一个插件类,比如TestPlugin。 3. 在插件类中使用tapable实例的方法,在webpack的emit钩子函数执行时触发我们定义的函数。你可以使用compiler.hooks.emit.tap方法来绑定钩子函数。 4. 在钩子函数内部,使用compiler.outputFileSystem.writeFile方法创建一个自定义的文件,并在文件中写入你想要的内容。比如你可以写入一句话,如"//我们的第一个webpack插件!"。 5. 在webpack配置文件中引入你的插件,并将它作为一个实例加入到plugins数组中。 以下是一个示例的webpack配置文件,展示了如何引入TestPlugin插件: ``` const path = require('path'); const TestPlugin = require('./TestPlugin'); module.exports = function() { return { mode: 'development', entry: ['./src/app.js'], output: { path: path.join(__dirname, 'out'), filename: 'out.[name].js' }, plugins: [ new TestPlugin() ] }; }; ``` 通过以上步骤,你就可以手写一个简单的webpack插件了。这个插件会在webpack输出的文件夹中创建一个自定义的文件,并在文件中写入一句话。你可以根据自己的需求,进一步扩展和定制插件的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值