原来rollup这么简单之插件篇

大家好,我是小雨小雨,致力于分享有趣的、实用的技术文章。

内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和大家一起进步。

大家的支持是我创作的动力。

计划

rollup系列打算一章一章的放出,内容更精简更专一更易于理解

这是rollup系列的最后一篇文章,以下是所有文章链接。

rollup.rollup

rollup.generate + rollup.write

rollup.watch

tree shaking

plugins <==== 当前文章

TL;DR

rollup的插件和其他大型框架大同小异,都是提供统一的标准接口,通过约定大于配置定义公共配置,注入当前构建结果相关的属性与方法,供开发者进行增删改查操作。为稳定可持续增长提供了强而有力的铺垫!

但不想webpack区分loader和plugin,rollup的plugin既可以担任loader的角色,也可以胜任传统plugin的角色。rollup提供的钩子函数是核心,比如load、transform对chunk进行解析更改,resolveFileUrl可以对加载模块进行合法解析,options对配置进行动态更新等等~

注意点

所有的注释都在这里,可自行阅读

!!!提示 => 标有TODO为具体实现细节,会视情况分析。

!!!注意 => 每一个子标题都是父标题(函数)内部实现

!!!强调 => rollup中模块(文件)的id就是文件地址,所以类似resolveID这种就是解析文件地址的意思,我们可以返回我们想返回的文件id(也就是地址,相对路径、决定路径)来让rollup加载

rollup是一个核心,只做最基础的事情,比如提供, 比如打包成不同风格的内容,我们的插件中提供了加载文件路径,解析文件内容(处理ts,sass等)等操作,是一种插拔式的设计,和webpack类似
插拔式是一种非常灵活且可长期迭代更新的设计,这也是一个中大型框架的核心,人多力量大嘛~

主要通用模块以及含义

Graph: 全局唯一的图,包含入口以及各种依赖的相互关系,操作方法,缓存等。是rollup的核心

PathTracker: 引用(调用)追踪器

PluginDriver: 插件驱动器,调用插件和提供插件环境上下文等

FileEmitter: 资源操作器

GlobalScope: 全局作用局,相对的还有局部的

ModuleLoader: 模块加载器

NodeBase: ast各语法(ArrayExpression、AwaitExpression等)的构造基类

插件机制分析

rollup的插件其实一个普通的函数,函数返回一个对象,该对象包含一些基础属性(如name),和不同阶段的钩子函数,像这个样子:

function plugin(options = {}) { return { name: 'rollup-plugin', transform() { return { code: 'code', map: { mappings: '' } }; } }; }

这里是官方建议遵守的.

我们平常书写rollup插件的时候,最关注的就是钩子函数部分了,钩子函数的调用时机有三类:

const chunks = rollup.rollup执行期间的

chunks.generator(write)执行期间的

监听文件变化并重新执行构建的rollup.watch执行期间的watchChange钩子函数

除了类别不同,rollup也提供了几种钩子函数的执行方式,每种方式都又分为同步或异步,方便内部使用:

async: 处理promise的异步钩子,也有同步版本

first: 如果多个插件实现了相同的钩子函数,那么会串式执行,从头到尾,但是,如果其中某个的返回值不是null也不是undefined的话,会直接终止掉后续插件。

sequential: 如果多个插件实现了相同的钩子函数,那么会串式执行,按照使用插件的顺序从头到尾执行,如果是异步的,会等待之前处理完毕,在执行下一个插件。

parallel: 同上,不过如果某个插件是异步的,其后的插件不会等待,而是并行执行。

文字表达比较苍白,咱们看几个实现:

钩子函数: hookFirst
使用场景:resolveId、resolveAssetUrl等

function hookFirst<H extends keyof PluginHooks, R = ReturnType<PluginHooks[H]>>( hookName: H, args: Args<PluginHooks[H]>, replaceContext?: ReplaceContext | null, skip?: number | null ): EnsurePromise<R> { // 初始化promise let promise: Promise<any> = Promise.resolve(); // this.plugins在初始化Graph的时候,进行了初始化 for (let i = 0; i < this.plugins.length; i++) { if (skip === i) continue; // 覆盖之前的promise,换言之就是串行执行钩子函数 promise = promise.then((result: any) => { // 返回非null或undefined的时候,停止运行,返回结果 if (result != null) return result; // 执行钩子函数 return this.runHook(hookName, args as any[], i, false, replaceContext); }); } // 最后一个promise执行的结果 return promise; }

钩子函数: hookFirstSync
使用场景:resolveFileUrl、resolveImportMeta等

// hookFirst的同步版本,也就是并行执行 function hookFirstSync<H extends keyof PluginHooks, R = ReturnType<PluginHooks[H]>>( hookName: H, args: Args<PluginHooks[H]>, replaceContext?: ReplaceContext ): R { for (let i = 0; i < this.plugins.length; i++) { // runHook的同步版本 const result = this.runHookSync(hookName, args, i, replaceContext); // 返回非null或undefined的时候,停止运行,返回结果 if (result != null) return result as any; } // 否则返回null return null as any; }

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

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