rollup 插件开发 - 构建钩子

本文详细介绍了Rollup插件开发中各种构建阶段钩子的功能、用法和注意事项,包括options钩子、buildStart、resolveId、load、transform等,帮助开发者更好地理解和利用这些钩子优化构建过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 将为其他插件触发此钩子,否则所有剩余的插件都将被跳过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值