Tapable&Hook&Plugins

本文详细解释了Webpack中的命令追踪机制,以及Hook和Tapable在Webpack构建过程中的重要性,特别是Compiler和Compilation对象的作用,以及entryOption、emit和additionPass等关键生命周期钩子的使用。
摘要由CSDN通过智能技术生成

以极客时间《玩转Webpack》课程学习为主的记录笔记。

源码解读

  1. webpack的命令跟踪,从node_modules/webpack/bin/ 可以看到命令内容,webpack会查看是否下载安装了webpack-cli / webpack-command。
  2. 使用webpack-cli 解析命令行信息、安装使用到的依赖,启动webpack构建。

Hook和Tapable

  1. 从webpack的文件(/node_modules/webpack/lib/webpack.js)中可见:根绝传入的options,将生成多个不同的compiler实例。
  2. Compiler(/node_modules/webpack/lib/Compiler.js) / Complization
    Compiler、Complization继承了Tapable对象,内部实现了很多的hooks。
    常见Compiler Hooks
  • entryOption:在entry配置项处理过之后,执行一个插件。
  • emit:在生成资源到输出目录之前执行。
  • done:在编译完成时执行。

常见Compilation Hooks

  • buildModule:在模块构建之前执行。
  • optimize:在优化阶段开始时执行。
  • optimizeChunks:在块优化阶段时才执行。
  • afterSeal:完成封装之后执行。
Compiler

Compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性创建,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中执行构建时,Compiler 对象负责处理所有事务。
当你运行 webpack 命令时,一个 Compiler 实例会被创建,并且在其生命周期中,它会对每个构建创建出一个新的 Compilation 对象。
Compiler 对象可以访问所有的 webpack 环境特定的功能,包括起始选项,构建的所有配置,以及注册的所有插件等。

Compilation

Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 webpack 以开发模式运行时,每当检测到一个文件变化,就会创建一个新的 Compilation,意味着 Compilation 对象描述的是一次单一的版本构建和生成资源的过程。
在构建过程中,Compilation 实例会被用来收集从入口开始的所有依赖,对这些依赖进行处理、优化、分割等操作,并最终输出编译后的资源文件到文件系统。
总结一下,Compiler 是一个 webpack 构建过程中的全局上下文,而 Compilation 是对于单次构建的上下文抽象。开发者可以通过 hooks 挂钩到 CompilerCompilation 的生命周期事件中,以扩展和控制构建过程。例如,plugin 通过监听特定的事件,可以在某个特定的构建时刻介入构建流程,添加模块、改变输出资源等。

// 源码
class Compiler {
	/**
	 * @param {string} context the compilation path
	 * @param {WebpackOptions} options options
	 */
	constructor(context, options = /** @type {WebpackOptions} */ ({})) {
		this.hooks = Object.freeze({
			/** @type {SyncHook<[]>} */
			initialize: new SyncHook([]),

			/** @type {SyncBailHook<[Compilation], boolean | undefined>} */
			shouldEmit: new SyncBailHook(["compilation"]),
			/** @type {AsyncSeriesHook<[Stats]>} */
			done: new AsyncSeriesHook(["stats"]),
			/** @type {SyncHook<[Stats]>} */
			afterDone: new SyncHook(["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<[string, AssetEmittedInfo]>} */
			assetEmitted: new AsyncSeriesHook(["file", "info"]),
			/** @type {AsyncSeriesHook<[Compilation]>} */
			afterEmit: new AsyncSeriesHook(["compilation"]),

			/** @type {SyncHook<[Compilation, CompilationParams]>} */
			thisCompilation: new SyncHook(["compilation", "params"]),
			/** @type {SyncHook<[Compilation, CompilationParams]>} */
			compilation: new SyncHook(["compilation", "params"]),
			/** @type {SyncHook<[NormalModuleFactory]>} */
			normalModuleFactory: new SyncHook(["normalModuleFactory"]),
			/** @type {SyncHook<[ContextModuleFactory]>}  */
			contextModuleFactory: new SyncHook(["contextModuleFactory"]),

			/** @type {AsyncSeriesHook<[CompilationParams]>} */
			beforeCompile: new AsyncSeriesHook(["params"]),
			/** @type {SyncHook<[CompilationParams]>} */
			compile: new SyncHook(["params"]),
			/** @type {AsyncParallelHook<[Compilation]>} */
			make: new AsyncParallelHook(["compilation"]),
			/** @type {AsyncParallelHook<[Compilation]>} */
			finishMake: new AsyncSeriesHook(["compilation"]),
			/** @type {AsyncSeriesHook<[Compilation]>} */
			afterCompile: new AsyncSeriesHook(["compilation"]),

			/** @type {AsyncSeriesHook<[]>} */
			readRecords: new AsyncSeriesHook([]),
			/** @type {AsyncSeriesHook<[]>} */
			emitRecords: new AsyncSeriesHook([]),

			/** @type {AsyncSeriesHook<[Compiler]>} */
			watchRun: new AsyncSeriesHook(["compiler"]),
			/** @type {SyncHook<[Error]>} */
			failed: new SyncHook(["error"]),
			/** @type {SyncHook<[string | null, number]>} */
			invalid: new SyncHook(["filename", "changeTime"]),
			/** @type {SyncHook<[]>} */
			watchClose: new SyncHook([]),
			/** @type {AsyncSeriesHook<[]>} */
			shutdown: new AsyncSeriesHook([]),

			/** @type {SyncBailHook<[string, string, any[]], true>} */
			infrastructureLog: new SyncBailHook(["origin", "type", "args"]),

			// TODO the following hooks are weirdly located here
			// TODO move them for webpack 5
			/** @type {SyncHook<[]>} */
			environment: new SyncHook([]),
			/** @type {SyncHook<[]>} */
			afterEnvironment: new SyncHook([]),
			/** @type {SyncHook<[Compiler]>} */
			afterPlugins: new SyncHook(["compiler"]),
			/** @type {SyncHook<[Compiler]>} */
			afterResolvers: new SyncHook(["compiler"]),
			/** @type {SyncBailHook<[string, Entry], boolean>} */
			entryOption: new SyncBailHook(["context", "entry"])
		});

		this.webpack = webpack;
        // ......
	}
    // ....
}
webpack可以理解为一种基于事件流的编程范例,一系列的插件运行。内部实现订阅和通知功能的模块就是Tapable。

Tapable

Tapable是一个类似于Nodejs EventEmiter的库,用于控制hooks的发布订阅功能的一个模块。

Tapable暴露了很多Hook,为插件提供挂载的钩子
const{
SyncHook, //同步钩子
SyncBailHook, //同步熔断钩子 当函数有任何返回值,就会在当前执行函数停止(遇到return将直接返回)
SyncWaterfallHook, //同步流水钩子 运行结果可以传递给下一个插件
SyncLoopHok, //同步循环钩子 监听函数返回true表示继续循环,返回undefine表示结束循环
AsyncParallelHook, //异步并发钩子
AsyncParallelBailHook, //异步并发熔断钩子
AsyncSeriesHook, //异步串行钩子
AsyncSeriesBailHook, //异步串行熔断钩子
AsyncSeriesWaterfallHook //异步串行流水钩子
} = require(“tapable”);

使用:const hook1 = new SyncHook(['argu1','argu2',])

使用方式:

Tabpack提供了同步&异步绑定钩子的方法,并且他们都有绑定事件和执行事件对
应的方法。

AsyncSync
绑定:tapAsync/tapPromise/tap绑定:tap
执行:callAsync/promise执行:call

简单用法:

const hook1 = new SyncHook(["arg1","arg2","arg3"]); 

//绑定事件流到webapck事件流
hook1.tap('hook1',(arg1,arg2,arg3)=>console.log(arg1,arg2,arg3))//1,2,3 

//执行绑定的事件
hook1.call(1,2,3)

自测书写一个Plugin订阅构建实例(Compiler)中提供的hook,使用Compilation

Plugin的固定写法为:

  1. 暴露一个类。
  2. 类中存在webpack的调用入口:apply,webpack在初始化时,将主动执行Myplugin.apply,将compiler构建实例传入插件中,供挂载订阅hook节点使用。
  3. 看下方的实现代码,所有的发布hook都在compiler.hooks中,compiler.hooks.xxx就是发布的事件名称,订阅流程节点信息就是使用tap(同步)/ tapAsync(异步)绑定上事件名和方法。
// eg1:
// const Compiler = require('./Compiler')
 
class MyPlugin{
    constructor() {
 
    }
    apply(compiler){
        compiler.hooks.calculateRoutes.tapPromise("calculateRoutes tapAsync", (source, target, routesList) => {
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log(`tapPromise to ${source} ${target} ${routesList}`)
                    resolve();
                },1000)
            });
        });
    }
}
 
const myPlugin = new MyPlugin();
// eg2:使用`emit`钩子在每次构建完成后输出一个文本文件
class MyExampleWebpackPlugin {
  // 插件必须具有apply方法
  apply(compiler) {
    // 确定在哪个阶段插入该插件
    compiler.hooks.emit.tapAsync('MyExampleWebpackPlugin', (compilation, callback) => {
      // 创建一份新的资源
      const mySource = '这是我的插件添加的资源内容';
      // 向Webpack输出列表中添加资源
      compilation.assets['my-plugin-output.txt'] = {
        source: function() {
          return mySource;
        },
        size: function() {
          return mySource.length;
        }
      };

      // 执行回调,表示插件处理完成
      callback();
    });
  }
}

module.exports = MyExampleWebpackPlugin;

webpack流程

Chunk 生成算法
  1. webpack 先将 entry 中对应的 module 都生成一个新的chunk
  2. 遍历 module 的依赖列表,将依赖的 module 也加入到chunk中
  3. 如果一个依赖 module 是动态引入的模块,那么就会根据这个module创建一个新的 chunk,继续遍历依赖
  4. 重复上面的过程,直至得到所有的 chunks
AST

抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntaxtree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。
在线demo: https://esprima.org/demo/parse.html

loader开发

一个最简单的loader栗子,loader主要用于"翻译代码变成JS引擎可识别的代码“

module.exports = function(source) {
  console.log ('loader a is executed');
  return source;
};
建议使用loader-runner来简化开发流程。

loader-runner可以提供一个独立的loader运行环境,不用下载webpack,有利于调试。

// 使用举例
// 新建一个命令文件 run-loader.js:

const fs = require("fs");
const path = require("path");
const { runLoaders } = require("loader-runner");

runLoaders(
	{
		resource: "./demo.txt",		// 要翻译的对象文件
		loaders: [path.resolve(__dirname, "./loaders/rawloader")], 		// 当前loader在的地方
		readResource: fs.readFile.bind(fs), 	// 读取文件的方式
	},
	(err, result) => (err ? console.error(err) : console.log(result))
);
运行查看结果:
node run-loader.js
  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Compiled with problems:X ERROR in ./node_modules/pdfjs-dist/es5/build/pdf.worker.js (./node_modules/vue-pdf/node_modules/worker-loader/dist/cjs.js!./node_modules/pdfjs-dist/es5/build/pdf.worker.js) Module build failed (from ./node_modules/vue-pdf/node_modules/worker-loader/dist/cjs.js): Error: MainTemplate.hooks.hotBootstrap has been removed (use your own RuntimeModule instead) at Object.tap (D:\newtest\node_modules\webpack\lib\MainTemplate.js:215:12) at WebWorkerMainTemplatePlugin.apply (D:\newtest\node_modules\vue-pdf\node_modules\webpack\lib\webworker\WebWorkerMainTemplatePlugin.js:139:35) at D:\newtest\node_modules\vue-pdf\node_modules\webpack\lib\webworker\WebWorkerTemplatePlugin.js:16:39 at Hook.eval [as call] (eval at create (D:\newtest\node_modules\tapable\lib\HookCodeFactory.js:19:10), <anonymous>:7:1) at Hook.CALL_DELEGATE [as _call] (D:\newtest\node_modules\tapable\lib\Hook.js:14:14) at Compiler.newCompilation (D:\newtest\node_modules\webpack\lib\Compiler.js:1121:30) at D:\newtest\node_modules\webpack\lib\Compiler.js:1166:29 at Hook.eval [as callAsync] (eval at create (D:\newtest\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:24:1) at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (D:\newtest\node_modules\tapable\lib\Hook.js:18:14) at Compiler.compile (D:\newtest\node_modules\webpack\lib\Compiler.js:1161:28) at Compiler.runAsChild (D:\newtest\node_modules\webpack\lib\Compiler.js:561:8) at Object.pitch (D:\newtest\node_modules\vue-pdf\node_modules\worker-loader\dist\index.js:115:19)
06-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值