rollup 插件开发 - 构建钩子
options
options(options: InputOptions) => InputOptions | null
这是构建阶段的第一个钩子,用于替换传递给 rollup 的选项对象。返回 null 不会替换任何内容,不允许添加新参数(允许配置的参数 cache, context, experimentalCacheExpiry, experimentalLogSideEffects, external, input, logLevel, makeAbsoluteExternalsRelative, maxParallelFileOps, moduleContext, onLog, onwarn, perf, plugins, preserveEntrySignatures, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch),否则会提示错误。
{
plugins: [
{
name: 'plugin-1',
options: function(options) {
// 会报错 Unknown input options: newArgs
return Object.assign(options, {newArgs: true})
}
}
]
}
如果只需要读取选项,则建议使用 buildStart 钩子,因为该钩子可以访问所有 options 钩子的转换考虑后的选项。
如 onLog 钩子一样,options 钩子不具有大多数插件上下文实用程序函数的访问权限,因为它可能在 Rollup 完全配置之前运行。唯一支持的属性是 this.meta 以及 this.error、this.warn、this.info 和 this.debug 用于记录和错误。
{
plugins: [
{
name: 'plugin-1',
// options: (options) => {} 箭头函数里没有this,不能用箭头函数写法
options: function(options) {
console.log(this);
}
},
{
name: 'plugin-2',
options: function() {
console.log('options2')
}
}
]
}
上面的代码会依次执行 plugin-1 和 plugin-2 的 options 方法,如果 plugin-1 的 options 方法是异步的,那么 plugin-2 的 options 方法会等到 plugin-1 的 options resolve 之后才执行。
buildStart
buildStart(options: InputOptions) => void
当我们需要访问传递给 rollup.rollup() 的选项配置时,建议使用此钩子,因为它考虑了所有 options 钩子的转换,并且还包含未设置选项的正确默认值。
{
plugins: [
{
name: 'plugin-1',
buildStart: async function(options) {
await asyncFunction();
console.log('buildStart1')
}
},
{
name: 'plugin-2',
buildStart: function() {
console.log('buildStart2')
}
},
{
name: 'plugin-3',
buildStart: function() {
console.log('buildStart3')
}
}
]
}
// 输出
// buildStart2
// buildStart3
// buildStart1
上面的代码中,plugin-1 的 buildStart 是异步的,plugin-2、plugin-3 的 buildStart 是同步的,因此在执行 plugin-1 的异步 buildStart 方法是会同步执行 plugin-2、plugin-3 的 buildStart 方法,plugin-2、plugin-3 都是同步的,因此会按照插件的顺序依次执行。
resolveId
resolveId(
source: string,
importer: string | undefined,
options: { attributes: Record<string, string>; custom?: CustomPluginOptions; isEntry: boolean }
) => ResolveIdResult;
type ResolveIdHook = (
source: string,
importer: string | undefined,
options: {
attributes: Record<string, string>;
custom?: { [plugin: string]: any };
isEntry: boolean;
}
) => ResolveIdResult;
type ResolveIdResult = string | null | false | PartialResolvedId;
interface PartialResolvedId {
id: string;
external?: boolean | 'absolute' | 'relative';
attributes?: Record<string, string> | null;
meta?: { [plugin: string]: any } | null;
moduleSideEffects?: boolean | 'no-treeshake' | null;
resolvedBy?: string | null;
syntheticNamedExports?: boolean | string | null;
}
定义一个自定义 id 解析器。解析器可以用于定位第三方依赖项等。source 就是导入语句中的导入目标,比如:
import { foo } from '../bar.js';
resolveId 函数中接收的 source 参数就是 “…/bar.js”。importer 是导入模块的解析完全后的 id(文件的绝对路径)。
在解析入口点时,importer 参数通常为 undefined。
如果返回 null,则会转而使用其他 resolveId 函数,最终使用默认的解析行为。
如果返回 false,则表示 source 应被视为外部模块,不会包含在产物中。
如果这发生在相对导入中,则会像使用 external 选项时一样重新规范化 id。
{
plugins: [
{
name: 'plugin-1',
resolveId: function (id) {
if (id === 'react') {
return false;
}
},
},
{
name: 'plugin-2',
// 如果 plugin-1 中的 resolveId 返回的是 false,那么 plugin-2 的 resolveId 不会执行。
resolveId: function (id) {
console.log(id)
},
}
]
}
如果返回一个对象,则可以将导入解析为不同的 id,同时将其从产物中排除。这使我们可以将依赖项替换为外部依赖项,而无需用户手动通过 external 选项将它们挪到产物之外:
{
plugins: [
{
name: 'plugin-1',
resolveId: function (id) {
if (id === './my-dependency') {
return {id: 'dependency-develop', external: true};
}
return null;
},
}
]
}
上述代码将 import ‘./my-dependency’ 这个相对引入重新写成 ‘dependency-develop’ 并设置成外部依赖项,这样打包的产物中就不会包含 ./my-dependency 文件中的内容。
load
load(id: string) => LoadResult
type LoadResult = string | null | SourceDescription;
interface SourceDescription {
code: string;
map?: string | SourceMap;
ast?: ESTree.Program;
attributes?: { [key: string]: string } | null;
meta?: { [plugin: string]: any } | null;
moduleSideEffects?: boolean | 'no-treeshake' | null;
syntheticNamedExports?: boolean | string | null;
}
接收一个正在解析的文件 id(绝对),可以自定义一个加载器。返回 null 将推迟到其他插件的 load 函数(最终是从文件系统加载的默认行为)。如果返回了一个字符串或者对象,那么就不会在执行剩余插件的 load 钩子函数。
返回对象包含的字段:
- code:代码字符串。
- map?:string | SourceMap 字符串或者 sourcemap
- ast?:生成一个标准的 ESTree AST。
- attributes?:{ [key: string]: string } | null。包括导入此模块时使用的导入属性。目前,它们不会影响产物模块的呈现,而是用于文档目的。如果返回 null 或省略标志,则 attributes 将由第一个解析此模块的 resolveId 钩子或此模块的第一个导入中存在的断言确定。transform 钩子可以覆盖此设置。
- meta?:{ [plugin: string]: any } | null。元数据应始终是可 JSON.stringify 的,并将在缓存中持久化
- moduleSideEffects?: boolean | ‘no-treeshake’ | null。是否要对代码模块中具有副作用的代码进行处理。
- syntheticNamedExports?:boolean | string | null。合并命名导出。如果返回 null 或省略标志,则 syntheticNamedExports 将由第一个解析此模块的 resolveId 钩子确定,或者最终默认为 false。transform 钩子可以覆盖此设置。
为了防止在某些情况下(例如,此钩子已经使用 this.parse 生成了 AST)产生额外的解析开销,此钩子可以选择返回一个 { code, ast, map } 对象。
可以使用 this.getModuleInfo 在此钩子中查找 attributes、meta、moduleSideEffects 和 syntheticNamedExports 的先前值。
transform
transform(code: string, id: string) => TransformResult
type TransformResult = string | null | Partial<SourceDescription>;
interface SourceDescription {
code: string;
map?: string | SourceMap;
ast?: ESTree.Program;
attributes?: { [key: string]: string } | null;
meta?: { [plugin: string]: any } | null;
moduleSideEffects?: boolean | 'no-treeshake' | null;
syntheticNamedExports?: boolean | string | null;
}
可以被用来转换单个模块。
拿到 load 钩子函数返回的源码信息进行转换修改,返回内容与 load 钩子一样。
moduleParsed
moduleParsed(moduleInfo: ModuleInfo) => void
每次 Rollup 完全解析一个模块时,都会调用此钩子。与 transform 钩子不同,此钩子永远不会被缓存,可以用于获取有关缓存和其他模块的信息,包括 meta 属性的最终形状、code 和 ast。
此钩子将等待直到所有导入都已解析,以便 moduleInfo.importedIds、moduleInfo.dynamicallyImportedIds、moduleInfo.importedIdResolutions 和 moduleInfo.dynamicallyImportedIdResolutions 中的信息是完整且准确的。但是请注意,有关导入模块的信息可能不完整,因为可能稍后会发现其他导入者。如果需要此信息,请使用 buildEnd 钩子。
buildEnd
buildEnd(error?: Error) => void
在 Rollup 完成产物但尚未调用 generate 或 write 之前调用;也可以返回一个 Promise。如果在构建过程中发生错误,则将其传递给此钩子。
在 buildEnd 之后就会进入 输出钩子 部分。
onLog
onLog(level: LogLevel, log: RollupLog) => boolean \| null
此钩子是一个函数,它接收并过滤由 Rollup 和插件生成的日志和警告,然后将它们传递给 onLog 选项或打印到控制台。
如果此钩子返回 false,日志将会被过滤。否则,日志将会传递给下一个插件的 onLog 钩子、onLog 选项或打印到控制台。插件还可以通过将日志传递给 this.error、this.warn、this.info 或 this.debug 并返回 false 来改变日志的级别或将日志转换为报错。请注意,与其他插件钩子不同,这些函数不会添加或更改日志的属性,例如插件名称。另外,由 onLog 钩子生成的日志不会再次传递给同一个插件的 onLog 钩子。如果另一个插件在其自己的 onLog 钩子中对这样的日志做出响应并生成了另一个日志,那么这个日志也不会传递给原始的 onLog 钩子。
watchChange
watchChange(id: string, change: {event: 'create' \| 'update' \| 'delete'}) => void
此钩子可以在构建和输出生成阶段的任何时候触发。如果是这种情况,则当前构建仍将继续,但会安排一个新的构建在当前构建完成后开始,从 options 开始
在 --watch 模式下,每当 Rollup 检测到监视文件的更改时,就会通知插件。如果返回一个 Promise,则 Rollup 将等待 Promise 解析后再安排另一个构建。此钩子不能由输出插件使用。第二个参数包含更改事件的其他详细信息。
closeWatcher
closeWatcher() => void
此钩子可以在构建和输出生成阶段的任何时候触发。如果是这种情况,则当前构建仍将继续,但永远不会触发新的 watchChange 事件
在观察器进程即将关闭时通知插件,以便可以关闭所有打开的资源。如果返回一个 Promise,则 Rollup 将在关闭进程之前等待 Promise 解决。输出插件无法使用此钩子。
resolveDynamicImport
resolveDynamicImport(
specifier: string | AstNode,
importer: string,
options: { attributes: Record<string, string> }
) => ResolveIdResult;
为动态导入定义自定义解析器。返回 false 表示应将导入保留,不要传递给其他解析器,从而使其成为外部导入。与 resolveId 钩子类似,我们还可以返回一个对象,将导入解析为不同的 id,同时将其标记为外部导入。
如果动态导入作为字符串参数传递,则从此钩子返回的字符串将被解释为现有模块 id,而返回 null 将推迟到其他解析器,最终到达 resolveId。
在经过 moduleParsed 阶段后发现代码中存在 动态导入 部分,便会执行这个钩子,如果通过 resolveDynamicImport 钩子解析出来的 id 在之前的流程里就被加载过,那么会直接跳到 load,否则就要执行 resolveId 去解析 id。
如果动态导入未传递字符串作为参数,则此钩子可以访问原始 AST 节点以进行分析,并以以下方式略有不同:
- 如果所有插件都返回 null,则将导入视为 external,而不会产出警告。
- 如果返回字符串,则此字符串不会被解释为模块 id,而是用作导入参数的替换。插件有责任确保生成的代码有效。
- 要将此类导入解析为现有模块,仍然可以返回对象 {id,external}。
请注意,此钩子的返回值不会传递给 resolveId;如果需要访问静态解析算法,则可以在插件上下文中使用 this.resolve(source, importer)。
shouldTransformCachedModule
shouldTransformCachedModule(options: {
ast: AstNode;
code: string;
id: string;
meta: { [plugin: string]: any };
moduleSideEffects: boolean | 'no-treeshake';
syntheticNamedExports: boolean | string;
}) => boolean | NullValue;
如果使用 Rollup 缓存(例如在观察模式下或通过 JavaScript API 明确使用),如果在 load 钩子之后,加载的 code 与缓存副本的代码相同,则 Rollup 将跳过模块的 transform 钩子。为了防止这种情况,插件可以实现此钩子并返回 true,以丢弃缓存副本并转换模块。
此钩子还可用于查找已缓存的模块并访问其缓存的元信息。
如果插件没有返回布尔值,则 Rollup 将为其他插件触发此钩子,否则所有剩余的插件都将被跳过。