深入了解rollup(三)插件机制

引言

Rollup是一个JavaScript模块打包器,它可以将多个模块打包成一个单独的文件,以便在浏览器中使用。与其他打包工具相比,Rollup的主要优势在于它可以生成更小、更快的代码。在本文中,我们将深入了解Rollup的插件机制。


关于Rollup的工作原理、使用方法、摇树优化(tree shaking)可以看这篇 深入了解rollup(一)快速开始

关于Rollup的配置可以看这篇 深入了解rollup(二)常用配置


rollup插件机制概述

Rollup 插件是一个对象,具有属性]构建钩子输出生成钩子 中的一个或多个,并遵循我们的约定。插件应作为一个导出一个函数的包进行发布,该函数可以使用插件特定的选项进行调用并返回此类对象。

插件允许你通过例如在打包之前进行转译代码或在node_modules文件夹中查找第三方模块来自定义 Rollup 的行为。

属性

  • name: 插件的名称,用于在警告和错误消息中标识插件。

  • version: 插件的版本,用于插件间通信场景。

约定

  • 插件应该有一个明确的名称,并以rollup-plugin-作为前缀。

  • package.json中包含rollup-plugin关键字。

  • 如果插件使用"虚拟模块"(例如用于辅助函数),请使用\0前缀模块 ID。这可以防止其他插件尝试处理它。

构建钩子执行方式

钩子是在构建的各个阶段调用的函数。钩子可以影响构建的运行方式,提供关于构建的信息,或在构建完成后修改构建。有不同种类的钩子:

  • async:该钩子也可以返回一个解析为相同类型的值的 Promise;否则,该钩子被标记为 sync
  • first:如果有多个插件实现此钩子,则钩子按顺序运行,直到钩子返回一个不是 nullundefined 的值。
  • sequential:如果有多个插件实现此钩子,则所有这些钩子将按指定的插件顺序运行。如果钩子是 async,则此类后续钩子将等待当前钩子解决后再运行。
  • parallel:如果有多个插件实现此钩子,则所有这些钩子将按指定的插件顺序运行。如果钩子是 async,则此类后续钩子将并行运行,而不是等待当前钩子。

除了函数之外,钩子也可以是对象。在这种情况下,实际的钩子函数必须指定为 handler。这允许你提供更多的可选属性,以改变钩子的执行:

  • order: "pre" | "post" | null

如果有多个插件实现此钩子,则可以先运行此插件("pre"),最后运行此插件("post"),或在用户指定的位置运行(没有值或 null)。

javascript 复制代码
export default function resolveFirst() {
  return {
    name: 'resolve-first',
    resolveId: {
      order: 'pre',
      handler(source) {
        console.log(source);
        return null;
      }
    }
  };
}

输出生成钩子

输出生成钩子可以提供有关生成的产物的信息并在构建完成后修改构建。它们的工作方式和类型与 构建钩子 相同,但是对于每个调用 bundle.generate(outputOptions)bundle.write(outputOptions),它们都会单独调用。仅使用输出生成钩子的插件也可以通过输出选项传递,并且因此仅针对某些输出运行。

钩子执行顺序

  1. 通过 options 钩子读取配置,并进行配置的转换,得到处理后的配置对象。

  2. 调用 buildStart 钩子,考虑了所有 options钩子配置的转换,包含未设置选项的正确默认值,正式开始构建流程。

  3. 调用 resolveId 钩子解析模块文件路径。rollup中模块文件的id就是文件地址,所以,类似resolveId这种就是解析文件地址的意思。从inputOptioninput配置指定的入口文件开始,每当匹配到引入外部模块的语句(如:import moudleA from './moduleA')便依次执行注册插件中的每一个 resolveId 钩子,直到某一个插件中的 resolveId 执行完后返回非 null 或非 undefined 的值,将停止执行后续插件的 resolveId 逻辑并进入下一个钩子。

  4. 调用load钩子加载模块内容,resolveId中的路径一般为相对路径,load中的路径为处理之后的绝对路径。

  5. 接着判断当前解析的模块是否存在缓存,若不存在则执行所有的 transform 钩子来对模块内容进行进行自定义的转换;若存在则判断shouldTransformCachedModule属性,true则执行所有的 transform 钩子,false则进入moduleParsed钩子逻辑。。

  6. 拿到最后的模块内容,进行 AST 分析,调用 moduleParsed 钩子。如果内部没有imports内容,进入buildEnd环节。如果还有imports内容则继续,如果是普通的 import,则执行resolveId 钩子,继续回到步骤3-调用resolveId ;如果是动态 import,则执行resolveDynamicImport 钩子解析路径,如果解析成功,则回到步骤4-load 加载模块,否则回到步骤3通过 resolveId 解析路径。

  7. 直到所有的 import 都解析完毕,Rollup 执行buildEnd钩子,Build阶段结束。

插件示例

javascript 复制代码
// rollup-plugin-example.js

export default function myExample () {
  return {
    name: 'my-example',
    options (options) {
      console.log({ options })
    },
    buildStart (options) {
      console.log("buildStart:", options)
    },
    resolveId (source,importer) {
      console.log("resolveId(source):", source)
      console.log("resolveId(importer):", importer)
      return null; 
    },
    load (id) {
      console.log({ id })
      return null; 
    },
    transform(code,id) {
      console.log("transform");
      console.log("---",code)
      console.log("---",id)
    },
    moduleParsed (info) {
      console.log("moduleParsed:", info)
    },
    buildEnd() { 
      console.log("buildEnd");
    }
  };
}

调用虚拟模块插件示例

javascript 复制代码
const virtualModuleId = 'virtual-module';
// rollup约定插件使用"虚拟模块",使用\0前缀模块 ID。这可以防止其他插件尝试处理它。
const resolvedVirtualModuleId = '\0' + virtualModuleId;
export default function virtualModule() {
  return {
    name: 'virtual-module', 
    resolveId (source) {
      if (source === 'virtual-module') { 
        return resolvedVirtualModuleId; // 告诉Rollup,这个ID是外部模块,不要在此处查找它
      }
      return null; // 其他ID应按通常方式处理
    },
    load (id) {
      console.log({ id })
      if (id === resolvedVirtualModuleId) { 
        // return 'export default "This is virtual!"'; // 告诉Rollup,如何加载此模块
        return 'export default function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }'
      }
      return null; // 其他ID应按通常方式处理
    },
  };
}

界面调用

javascript 复制代码
// index.js
import fib from "virtual-module";
console.log(fib(10))

build之后

js 复制代码
// index.js
function fib(n) {return n <= 1 ? n : fib(n - 1) + fib(n - 2)}
console.log(fib(10));

总结

Rollup的插件机制通过定义钩子函数来扩展其功能,钩子函数在不同的阶段执行不同的操作。开发者可以根据自己的需求编写自定义插件,并将其添加到Rollup配置中。通过使用插件机制,可以实现各种功能扩展,例如修改配置选项、解析模块路径、加载模块内容、转换模块代码等。

相关推荐
To_OC1 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic1 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy1 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志2 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble3 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜4 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森5 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒6 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端