原来rollup这么简单之插件篇 (2)

钩子函数: hookSeq
使用场景:onwrite、generateBundle等

// 和hookFirst的区别就是不能中断 async function hookSeq<H extends keyof PluginHooks>( hookName: H, args: Args<PluginHooks[H]>, replaceContext?: ReplaceContext ): Promise<void> { let promise: Promise<void> = Promise.resolve(); for (let i = 0; i < this.plugins.length; i++) promise = promise.then(() => this.runHook<void>(hookName, args as any[], i, false, replaceContext) ); return promise; }

钩子函数: hookParallel
使用场景:buildStart、buildEnd、renderStart等

// 同步进行,利用的Promise.all function hookParallel<H extends keyof PluginHooks>( hookName: H, args: Args<PluginHooks[H]>, replaceContext?: ReplaceContext ): Promise<void> { // 创建promise.all容器 const promises: Promise<void>[] = []; // 遍历每一个plugin for (let i = 0; i < this.plugins.length; i++) { // 执行hook返回promise const hookPromise = this.runHook<void>(hookName, args as any[], i, false, replaceContext); // 如果没有那么不push if (!hookPromise) continue; promises.push(hookPromise); } // 返回promise return Promise.all(promises).then(() => {}); }

钩子函数: hookReduceArg0
使用场景: outputOptions、renderChunk等

// 对arg第一项进行reduce操作 function hookReduceArg0<H extends keyof PluginHooks, V, R = ReturnType<PluginHooks[H]>>( hookName: H, [arg0, ...args]: any[], // 取出传入的数组的第一个参数,将剩余的置于一个数组中 reduce: Reduce<V, R>, replaceContext?: ReplaceContext // 替换当前plugin调用时候的上下文环境 ) { let promise = Promise.resolve(arg0); // 默认返回source.code for (let i = 0; i < this.plugins.length; i++) { // 第一个promise的时候只会接收到上面传递的arg0 // 之后每一次promise接受的都是上一个插件处理过后的source.code值 promise = promise.then(arg0 => { const hookPromise = this.runHook(hookName, [arg0, ...args], i, false, replaceContext); // 如果没有返回promise,那么直接返回arg0 if (!hookPromise) return arg0; // result代表插件执行完成的返回值 return hookPromise.then((result: any) => reduce.call(this.pluginContexts[i], arg0, result, this.plugins[i]) ); }); } return promise; }

通过观察上面几种钩子函数的调用方式,我们可以发现,其内部有一个调用钩子函数的方法: runHook(Sync),该函数执行插件中提供的钩子函数。

实现很简单:

function runHook<T>( hookName: string, args: any[], pluginIndex: number, permitValues: boolean, hookContext?: ReplaceContext | null ): Promise<T> { this.previousHooks.add(hookName); // 找到当前plugin const plugin = this.plugins[pluginIndex]; // 找到当前执行的在plugin中定义的hooks钩子函数 const hook = (plugin as any)[hookName]; if (!hook) return undefined as any; // pluginContexts在初始化plugin驱动器类的时候定义,是个数组,数组保存对应着每个插件的上下文环境 let context = this.pluginContexts[pluginIndex]; // 用于区分对待不同钩子函数的插件上下文 if (hookContext) { context = hookContext(context, plugin); } return Promise.resolve() .then(() => { // permit values allows values to be returned instead of a functional hook if (typeof hook !== 'function') { if (permitValues) return hook; return error({ code: 'INVALID_PLUGIN_HOOK', message: `Error running plugin hook ${hookName} for ${plugin.name}, expected a function hook.` }); } // 传入插件上下文和参数,返回插件执行结果 return hook.apply(context, args); }) .catch(err => throwPluginError(err, plugin.name, { hook: hookName })); }

当然,并不是每个人刚开始都会使用插件,所以rollup本身也提供了几个必需的钩子函数供我们使用,在Graph实例化的时候与用户自定义插件进行concat操作:

import { getRollupDefaultPlugin } from './defaultPlugin'; this.plugins = userPlugins.concat( // 采用内置默认插件或者graph的插件驱动器的插件,不管怎么样,内置默认插件是肯定有的 // basePluginDriver是上一个PluginDriver初始化的插件 // preserveSymlinks: 软连标志 basePluginDriver ? basePluginDriver.plugins : [getRollupDefaultPlugin(preserveSymlinks)] );

那rollup提供了哪些必需的钩子函数呢:

export function getRollupDefaultPlugin(preserveSymlinks: boolean): Plugin { return { // 插件名 name: 'Rollup Core', // 默认的模块(文件)加载机制,内部主要使用path.resolve resolveId: createResolveId(preserveSymlinks) as ResolveIdHook, // this.pluginDriver.hookFirst('load', [id])为异步调用,readFile内部用promise包装了fs.readFile,并返回该promise load(id) { return readFile(id); }, // 用来处理通过emitFile添加的urls或文件 resolveFileUrl({ relativePath, format }) { // 不同format会返回不同的文件解析地址 return relativeUrlMechanisms[format](relativePath); }, // 处理import.meta.url,参考地址:https://nodejs.org/api/esm.html#esm_import_meta) resolveImportMeta(prop, { chunkId, format }) { // 改变 获取import.meta的信息 的行为 const mechanism = importMetaMechanisms[format] && importMetaMechanisms[format](prop, chunkId); if (mechanism) { return mechanism; } } }; }

过一眼发现都是最基本处理路径解析内容的钩子函数。

不仅如此,rollup给钩子函数注入了context,也就是上下文环境,用来方便对chunks和其他构建信息进行增删改查。

中也写得很清楚,比如:

使用this.parse,调用rollup内部中的acron实例解析出ast

使用this.emitFile来增加产出的文件,看这个.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzfdf.html