引言
Rollup是一个JavaScript模块打包器,它可以将多个模块打包成一个单独的文件,以便在浏览器中使用。与其他打包工具相比,Rollup的主要优势在于它可以生成更小、更快的代码。在本文中,我们将深入了解Rollup的插件机制。
rollup插件机制概述
Rollup 插件是一个对象,具有属性]、构建钩子 和 输出生成钩子 中的一个或多个,并遵循我们的约定。插件应作为一个导出一个函数的包进行发布,该函数可以使用插件特定的选项进行调用并返回此类对象。
插件允许你通过例如在打包之前进行转译代码或在node_modules
文件夹中查找第三方模块来自定义 Rollup 的行为。
属性
-
name: 插件的名称,用于在警告和错误消息中标识插件。
-
version: 插件的版本,用于插件间通信场景。
约定
-
插件应该有一个明确的名称,并以
rollup-plugin-
作为前缀。 -
在
package.json
中包含rollup-plugin
关键字。 -
如果插件使用“虚拟模块”(例如用于辅助函数),请使用
\0
前缀模块 ID。这可以防止其他插件尝试处理它。
构建钩子执行方式
钩子是在构建的各个阶段调用的函数。钩子可以影响构建的运行方式,提供关于构建的信息,或在构建完成后修改构建。有不同种类的钩子:
async
:该钩子也可以返回一个解析为相同类型的值的 Promise;否则,该钩子被标记为sync
。first
:如果有多个插件实现此钩子,则钩子按顺序运行,直到钩子返回一个不是null
或undefined
的值。sequential
:如果有多个插件实现此钩子,则所有这些钩子将按指定的插件顺序运行。如果钩子是async
,则此类后续钩子将等待当前钩子解决后再运行。parallel
:如果有多个插件实现此钩子,则所有这些钩子将按指定的插件顺序运行。如果钩子是async
,则此类后续钩子将并行运行,而不是等待当前钩子。
除了函数之外,钩子也可以是对象。在这种情况下,实际的钩子函数必须指定为 handler
。这允许你提供更多的可选属性,以改变钩子的执行:
- order: "pre" | "post" | null
如果有多个插件实现此钩子,则可以先运行此插件("pre"
),最后运行此插件("post"
),或在用户指定的位置运行(没有值或 null
)。
export default function resolveFirst() {
return {
name: 'resolve-first',
resolveId: {
order: 'pre',
handler(source) {
console.log(source);
return null;
}
}
};
}
输出生成钩子
输出生成钩子可以提供有关生成的产物的信息并在构建完成后修改构建。它们的工作方式和类型与 构建钩子 相同,但是对于每个调用 bundle.generate(outputOptions)
或 bundle.write(outputOptions)
,它们都会单独调用。仅使用输出生成钩子的插件也可以通过输出选项传递,并且因此仅针对某些输出运行。
钩子执行顺序
-
通过
options
钩子读取配置,并进行配置的转换,得到处理后的配置对象。 -
调用
buildStart
钩子,考虑了所有options
钩子配置的转换,包含未设置选项的正确默认值,正式开始构建流程。 -
调用
resolveId
钩子解析模块文件路径。rollup中模块文件的id就是文件地址,所以,类似resolveId这种就是解析文件地址的意思。从inputOption
的input
配置指定的入口文件开始,每当匹配到引入外部模块的语句(如:import moudleA from './moduleA'
)便依次执行注册插件中的每一个resolveId
钩子,直到某一个插件中的resolveId
执行完后返回非null
或非undefined
的值,将停止执行后续插件的resolveId
逻辑并进入下一个钩子。 -
调用
load
钩子加载模块内容,resolveId
中的路径一般为相对路径,load中的路径为处理之后的绝对路径。 -
接着判断当前解析的模块是否存在缓存,若不存在则执行所有的
transform
钩子来对模块内容进行进行自定义的转换;若存在则判断shouldTransformCachedModule
属性,true则执行所有的transform
钩子,false则进入moduleParsed
钩子逻辑。。 -
拿到最后的模块内容,进行
AST
分析,调用moduleParsed
钩子。如果内部没有imports
内容,进入buildEnd
环节。如果还有imports
内容则继续,如果是普通的import
,则执行resolveId
钩子,继续回到步骤3-调用resolveId;如果是动态import
,则执行resolveDynamicImport
钩子解析路径,如果解析成功,则回到步骤4-load加载模块,否则回到步骤3通过resolveId
解析路径。 -
直到所有的
import
都解析完毕,Rollup
执行buildEnd
钩子,Build阶段结束。
插件示例
// 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");
}
};
}
调用虚拟模块插件示例
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应按通常方式处理
},
};
}
界面调用
// index.js
import fib from "virtual-module";
console.log(fib(10))
build之后
// index.js
function fib(n) {return n <= 1 ? n : fib(n - 1) + fib(n - 2)}
console.log(fib(10));
总结
Rollup的插件机制通过定义钩子函数来扩展其功能,钩子函数在不同的阶段执行不同的操作。开发者可以根据自己的需求编写自定义插件,并将其添加到Rollup配置中。通过使用插件机制,可以实现各种功能扩展,例如修改配置选项、解析模块路径、加载模块内容、转换模块代码等。