Skip to content

插件开发

插件概述

Rollup 插件是一个具有下面描述的 特性构建钩子输出生成钩子 中的一个或多个的对象,它遵循我们的 惯例。 插件应该作为包分发,该包导出可以使用插件特定选项调用的函数并返回这样的对象。

A Rollup plugin is an object with one or more of the properties, build hooks, and output generation hooks described below, and which follows our conventions. A plugin should be distributed as a package which exports a function that can be called with plugin specific options and returns such an object.

插件允许你自定义 Rollup 的行为,例如,在打包之前转译代码,或者在 node_modules 文件夹中查找第三方模块。 有关如何使用它们的示例,请参阅 使用插件

Plugins allow you to customise Rollup's behaviour by, for example, transpiling code before bundling, or finding third-party modules in your node_modules folder. For an example on how to use them, see Using plugins.

插件列表可以在 github.com/rollup/awesome 找到。 如果你想对插件提出建议,请提交 Pull Request。

A List of Plugins may be found at github.com/rollup/awesome. If you would like to make a suggestion for a plugin, please submit a Pull Request.

一个简单的例子

以下插件将拦截 virtual-module 的任何导入,而无需访问文件系统。 例如,如果你想在浏览器中使用 Rollup,这是必要的。 它甚至可以用来替换入口点,如示例所示。

The following plugin will intercept any imports of virtual-module without accessing the file system. This is for instance necessary if you want to use Rollup in a browser. It can even be used to replace entry points as shown in the example.

js
// rollup-plugin-my-example.js
export default function myExample () {
  return {
    name: 'my-example', // this name will show up in logs and errors
    resolveId ( source ) {
      if (source === 'virtual-module') {
        // this signals that Rollup should not ask other plugins or check
        // the file system to find this id
        return source;
      }
      return null; // other ids should be handled as usually
    },
    load ( id ) {
      if (id === 'virtual-module') {
        // the source code for "virtual-module"
        return 'export default "This is virtual!"';
      }
      return null; // other ids should be handled as usually
    }
  };
}

// rollup.config.js
import myExample from './rollup-plugin-my-example.js';
export default ({
  input: 'virtual-module', // resolved by our plugin
  plugins: [myExample()],
  output: [{
    file: 'bundle.js',
    format: 'es'
  }]
});
// rollup-plugin-my-example.js
export default function myExample () {
  return {
    name: 'my-example', // this name will show up in logs and errors
    resolveId ( source ) {
      if (source === 'virtual-module') {
        // this signals that Rollup should not ask other plugins or check
        // the file system to find this id
        return source;
      }
      return null; // other ids should be handled as usually
    },
    load ( id ) {
      if (id === 'virtual-module') {
        // the source code for "virtual-module"
        return 'export default "This is virtual!"';
      }
      return null; // other ids should be handled as usually
    }
  };
}

// rollup.config.js
import myExample from './rollup-plugin-my-example.js';
export default ({
  input: 'virtual-module', // resolved by our plugin
  plugins: [myExample()],
  output: [{
    file: 'bundle.js',
    format: 'es'
  }]
});

惯例

  • 插件应该有一个明确的名称,并带有 rollup-plugin- 前缀。
  • package.json 中包含 rollup-plugin 关键字。
  • 应该测试插件。 我们推荐 Mocha艾娃,它们支持开箱即用的 Promise。
  • 尽可能使用异步方法,例如 fs.readFile 而不是 fs.readFileSync
  • 用英语记录你的插件。
  • 如果适用,请确保你的插件输出正确的源映射。
  • 如果你的插件使用 'virtual modules'(例如用于辅助函数),请在模块 ID 前添加 \0 前缀。 这可以防止其他插件尝试处理它。

属性

name

类型:string

插件的名称,用于错误消息和日志。

The name of the plugin, for use in error messages and logs.

version

类型:string

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

The version of the plugin, for use in inter-plugin communication scenarios.

构建钩子

为了与构建过程进行交互,你的插件对象包括 "钩子"。 钩子是在构建的各个阶段调用的函数。 钩子可以影响构建的运行方式、提供有关构建的信息或在构建完成后修改构建。 有不同种类的钩子:

To interact with the build process, your plugin object includes "hooks". Hooks are functions which are called at various stages of the build. Hooks can affect how a build is run, provide information about a build, or modify a build once complete. There are different kinds of hooks:

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

钩子不仅可以是函数,还可以是对象。 在这种情况下,实际的钩子函数(或 banner/footer/intro/outro 的值)必须指定为 handler。 这允许你提供更改钩子执行的附加可选属性:

Instead of a function, hooks can also be objects. In that case, the actual hook function (or value for banner/footer/intro/outro) must be specified as handler. This allows you to provide additional optional properties that change hook execution:

  • order: "pre" | "post" | null
    如果有多个插件实现此钩子,请首先运行此插件("pre"),最后运行此插件("post"),或在用户指定的位置(无值或 null)运行。

    js
    export default function resolveFirst() {
    	return {
    		name: 'resolve-first',
    		resolveId: {
    			order: 'pre',
    			handler(source) {
    				if (source === 'external') {
    					return { id: source, external: true };
    				}
    				return null;
    			}
    		}
    	};
    }
    export default function resolveFirst() {
    	return {
    		name: 'resolve-first',
    		resolveId: {
    			order: 'pre',
    			handler(source) {
    				if (source === 'external') {
    					return { id: source, external: true };
    				}
    				return null;
    			}
    		}
    	};
    }

    如果多个插件使用 "pre""post",Rollup 会按照用户指定的顺序运行它们。 该选项可用于所有插件钩子。 对于并行钩子,它会更改钩子同步部分的运行顺序。

  • sequential: boolean
    不要与其他插件的同一钩子并行运行此钩子。 只能用于 parallel 钩子。 使用此选项将使 Rollup 等待所有先前插件的结果,然后执行插件钩子,然后再次并行运行其余插件。 例如。 当你有插件 ABCDE 都实现相同的并行钩子并且中间插件 Csequential: true 时,Rollup 将首先并行运行 A + B,然后单独运行 C,然后并行运行 D + E

    当你需要在相互依赖的不同 writeBundle 钩子中运行多个命令行工具时,这可能很有用(请注意,如果可能,建议在顺序 generateBundle 钩子中添加/删除文件,不过,速度更快,可以与 纯内存构建并允许其他内存构建插件查看文件)。 你可以将此选项与 order 结合使用以进行额外排序。

    js
    import { resolve } from 'node:path';
    import { readdir } from 'node:fs/promises';
    
    export default function getFilesOnDisk() {
    	return {
    		name: 'getFilesOnDisk',
    		writeBundle: {
    			sequential: true,
    			order: 'post',
    			async handler({ dir }) {
    				const topLevelFiles = await readdir(resolve(dir));
    				console.log(topLevelFiles);
    			}
    		}
    	};
    }
    import { resolve } from 'node:path';
    import { readdir } from 'node:fs/promises';
    
    export default function getFilesOnDisk() {
    	return {
    		name: 'getFilesOnDisk',
    		writeBundle: {
    			sequential: true,
    			order: 'post',
    			async handler({ dir }) {
    				const topLevelFiles = await readdir(resolve(dir));
    				console.log(topLevelFiles);
    			}
    		}
    	};
    }

构建钩子在构建阶段运行,由 rollup.rollup(inputOptions) 触发。 它们主要关注在 Rollup 处理输入文件之前对其进行定位、提供和转换。 构建阶段的第一个钩子是 options,最后一个钩子始终是 buildEnd。 如果出现构建错误,则之后将调用 closeBundle

Build hooks are run during the build phase, which is triggered by rollup.rollup(inputOptions). They are mainly concerned with locating, providing and transforming input files before they are processed by Rollup. The first hook of the build phase is options, the last one is always buildEnd. If there is a build error, closeBundle will be called after that.

parallel
sequential
first
async
sync

each entry
external
non-external
not cached
no imports
cached
false
true
each import()
non-external
each import
(cached)
each import
(not cached)
external
unresolved
watchChange
closeWatcher
buildEnd
buildStart
load
moduleParsed
options
resolveDynamicImport
resolveId
shouldTransformCachedModule
transform

此外,在监视模式下,可以随时触发 watchChange 钩子,以通知当前运行生成输出后将触发新的运行。 另外,当观察者关闭时,closeWatcher 钩子将被触发。

Additionally, in watch mode the watchChange hook can be triggered at any time to notify a new run will be triggered once the current run has generated its outputs. Also, when watcher closes, the closeWatcher hook will be triggered.

请参阅 输出生成钩子 了解在输出生成阶段运行以修改生成的输出的钩子。

See Output Generation Hooks for hooks that run during the output generation phase to modify the generated output.

buildEnd

类型:(error?: Error) => void
种类:异步、并行
以前的:moduleParsedresolveIdresolveDynamicImport
下一个:outputOptions 在输出生成阶段,因为这是构建阶段的最后一个钩子

当 Rollup 完成打包后,但在调用 generatewrite 之前调用; 你也可以返回一个 Promise。 如果构建期间发生错误,则会将其传递到此钩子。

Called when Rollup has finished bundling, but before generate or write is called; you can also return a Promise. If an error occurred during the build, it is passed on to this hook.

buildStart

类型:(options: InputOptions) => void
种类:异步、并行
以前的:options
下一个:resolveId 并行解析每个入口点

在每个 rollup.rollup 版本上调用。 当你需要访问传递给 rollup.rollup() 的选项时,建议使用此钩子,因为它考虑了所有 options 钩子的转换,并且还包含未设置选项的正确默认值。

Called on each rollup.rollup build. This is the recommended hook to use when you need access to the options passed to rollup.rollup() as it takes the transformations by all options hooks into account and also contains the right default values for unset options.

closeWatcher

类型:() => void
种类:异步、并行
上一页/下一页:在构建和输出生成阶段,可以随时触发该钩子。 如果是这种情况,当前构建仍将继续,但不会触发新的 watchChange 事件

当观察程序进程将关闭时通知插件,以便所有打开的资源也可以关闭。 如果返回 Promise,Rollup 将等待 Promise 解析后再关闭流程。 输出插件不能使用此钩子。

Notifies a plugin when the watcher process will close so that all open resources can be closed too. If a Promise is returned, Rollup will wait for the Promise to resolve before closing the process. This hook cannot be used by output plugins.

load

类型:(id: string) => LoadResult
种类:异步,首先
以前的:解析加载的 id 的 resolveIdresolveDynamicImport。 此外,通过调用 this.load 来预加载与 id 对应的模块,可以随时从插件钩子触发此钩子
下一个:如果没有使用缓存,或者没有具有相同 code 的缓存副本,则 transform 转换加载的文件,否则 shouldTransformCachedModule
typescript
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;
}
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;
}

定义自定义加载程序。 返回 null 遵循其他 load 函数(最终是从文件系统加载的默认行为)。 为了防止额外的解析开销,例如 由于某种原因,该钩子已使用 this.parse 生成 AST,该钩子可以选择返回 { code, ast, map } 对象。 ast 必须是标准 ESTree AST,每个节点具有 startend 属性。 如果转换不移动代码,你可以通过将 map 设置为 null 来保留现有源映射。 否则,你可能需要生成源映射。 请参阅有关 源代码转换 的部分。

Defines a custom loader. Returning null defers to other load functions (and eventually the default behavior of loading from the file system). To prevent additional parsing overhead in case e.g. this hook already used this.parse to generate an AST for some reason, this hook can optionally return a { code, ast, map } object. The ast must be a standard ESTree AST with start and end properties for each node. If the transformation does not move code, you can preserve existing sourcemaps by setting map to null. Otherwise, you might need to generate the source map. See the section on source code transformations.

如果为 moduleSideEffects 返回 false 并且没有其他模块从该模块导入任何内容,则该模块将不会包含在打包包中,即使该模块会产生副作用。 如果返回 true,Rollup 将使用其默认算法来包含模块中具有副作用(例如修改全局或导出变量)的所有语句。 如果返回 "no-treeshake",则该模块的 treeshaking 将被关闭,并且它也将包含在生成的块之一中,即使它是空的。 如果返回 null 或省略标志,则 moduleSideEffects 将由解析此模块的第一个 resolveId 钩子、treeshake.moduleSideEffects 选项确定,或最终默认为 truetransform 钩子可以覆盖它。

If false is returned for moduleSideEffects and no other module imports anything from this module, then this module will not be included in the bundle even if the module would have side effects. If true is returned, Rollup will use its default algorithm to include all statements in the module that have side effects (such as modifying a global or exported variable). If "no-treeshake" is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty. If null is returned or the flag is omitted, then moduleSideEffects will be determined by the first resolveId hook that resolved this module, the treeshake.moduleSideEffects option, or eventually default to true. The transform hook can override this.

attributes 包含导入此模块时使用的导入属性。 目前,它们不影响打包模块的渲染,而是用于文档目的。 如果返回 null 或省略该标志,则 attributes 将由解析此模块的第一个 resolveId 钩子或此模块的第一次导入中存在的属性确定。 transform 钩子可以覆盖它。

attributes contain the import attributes that were used when this module was imported. At the moment, they do not influence rendering for bundled modules but rather serve documentation purposes. If null is returned or the flag is omitted, then attributes will be determined by the first resolveId hook that resolved this module, or the attributes present in the first import of this module. The transform hook can override this.

请参阅 合成命名导出 了解 syntheticNamedExports 选项的效果。 如果返回 null 或省略标志,则 syntheticNamedExports 将由解析此模块的第一个 resolveId 钩子确定,或最终默认为 falsetransform 钩子可以覆盖它。

See synthetic named exports for the effect of the syntheticNamedExports option. If null is returned or the flag is omitted, then syntheticNamedExports will be determined by the first resolveId hook that resolved this module or eventually default to false. The transform hook can override this.

请参阅 自定义模块元数据 了解如何使用 meta 选项。 如果此钩子返回 meta 对象,它将与 resolveId 钩子返回的任何 meta 对象进行浅层合并。 如果没有钩子返回 meta 对象,它将默认为空对象。 transform 钩子可以进一步添加或替换该对象的属性。

See custom module meta-data for how to use the meta option. If a meta object is returned by this hook, it will be merged shallowly with any meta object returned by the resolveId hook. If no hook returns a meta object it will default to an empty object. The transform hook can further add or replace properties of this object.

你可以使用 this.getModuleInfo 找出该钩子中 attributesmetamoduleSideEffectssyntheticNamedExports 的先前值。

You can use this.getModuleInfo to find out the previous values of attributes, meta, moduleSideEffects and syntheticNamedExports inside this hook.

moduleParsed

类型:(moduleInfo: ModuleInfo) => void
种类:异步、并行
以前的:当前处理的文件被转换的 transform
下一个:resolveIdresolveDynamicImport 并行解析所有发现的静态和动态导入(如果存在),否则 buildEnd

每次 Rollup 完全解析模块时都会调用此钩子。 请参阅 this.getModuleInfo 了解传递给此钩子的信息。

This hook is called each time a module has been fully parsed by Rollup. See this.getModuleInfo for what information is passed to this hook.

transform 钩子相比,此钩子从不缓存,可用于获取有关缓存模块和其他模块的信息,包括 meta 属性、codeast 的最终形状。

In contrast to the transform hook, this hook is never cached and can be used to get information about both cached and other modules, including the final shape of the meta property, the code and the ast.

该钩子将等到所有导入都得到解决,以便 moduleInfo.importedIdsmoduleInfo.dynamicallyImportedIdsmoduleInfo.importedIdResolutionsmoduleInfo.dynamicallyImportedIdResolutions 中的信息完整且准确。 但请注意,有关导入模块的信息可能不完整,因为稍后可能会发现其他导入程序。 如果你需要此信息,请使用 buildEnd 钩子。

This hook will wait until all imports are resolved so that the information in moduleInfo.importedIds, moduleInfo.dynamicallyImportedIds, moduleInfo.importedIdResolutions, and moduleInfo.dynamicallyImportedIdResolutions is complete and accurate. Note however that information about importing modules may be incomplete as additional importers could be discovered later. If you need this information, use the buildEnd hook.

onLog

类型:(level: LogLevel, log: RollupLog) => boolean | null
种类:同步、顺序
上一页/下一页:该钩子可以随时触发。

有关可用的 Loglevel 值和 RollupLog 类型,请参阅 onLog 选项。

See the onLog option for the available Loglevel values and the RollupLog type.

一个函数,用于接收和过滤由 Rollup 和插件生成的日志和警告,然后将其传递到 onLog 选项或打印到控制台。

A function that receives and filters logs and warnings generated by Rollup and plugins before they are passed to the onLog option or printed to the console.

如果从此钩子返回 false,则日志将被过滤。 否则,日志将被交给下一个插件的 onLog 钩子,onLog 选项,或者打印到控制台。 插件还可以通过将日志传递给 this.errorthis.warnthis.infothis.debug 并返回 false 来更改日志的日志级别或将日志变为错误。 请注意,与其他添加例如的插件钩子不同 将插件名称添加到日志中,这些函数不会添加或更改日志的属性。 此外,onLog 钩子生成的日志不会传递回同一插件的 onLog 钩子。 如果另一个插件在其自己的 onLog 钩子中生成一条日志来响应此类日志,则该日志也不会传递到原始 onLog 钩子。

If false is returned from this hook, the log will be filtered. Otherwise, the log will be handed to the onLog hook of the next plugin, the onLog option, or printed to the console. Plugins can also change the log level of a log or turn a log into an error by passing the log to this.error, this.warn, this.info or this.debug and returning false. Note that unlike other plugin hooks that add e.g. the plugin name to the log, those functions will not add or change properties of the log. Additionally, logs generated by an onLog hook will not be passed back to the onLog hook of the same plugin. If another plugin generates a log in response to such a log in its own onLog hook, this log will not be passed to the original onLog hook, either.

js
function plugin1() {
	return {
		name: 'plugin1',
		buildStart() {
			this.info({ message: 'Hey', pluginCode: 'SPECIAL_CODE' });
		},
		onLog(level, log) {
			if (log.plugin === 'plugin1' && log.pluginCode === 'SPECIAL_CODE') {
				// We turn logs into warnings based on their code. This warnings
				// will not be passed back to the same plugin to avoid an
				// infinite loop, but other plugins will still receive it.
				this.warn(log);
				return false;
			}
		}
	};
}

function plugin2() {
	return {
		name: 'plugin2',
		onLog(level, log) {
			if (log.plugin === 'plugin1' && log.pluginCode === 'SPECIAL_CODE') {
				// You can modify logs in this hooks as well
				log.meta = 'processed by plugin 2';
				// This turns the log back to "info". If this happens in
				// response to the first plugin, it will not be passed back to
				// either plugin to avoid an infinite loop. If both plugins are
				// active, the log will be an info log if the second plugin is
				// placed after the first one
				this.info(log);
				return false;
			}
		}
	};
}
function plugin1() {
	return {
		name: 'plugin1',
		buildStart() {
			this.info({ message: 'Hey', pluginCode: 'SPECIAL_CODE' });
		},
		onLog(level, log) {
			if (log.plugin === 'plugin1' && log.pluginCode === 'SPECIAL_CODE') {
				// We turn logs into warnings based on their code. This warnings
				// will not be passed back to the same plugin to avoid an
				// infinite loop, but other plugins will still receive it.
				this.warn(log);
				return false;
			}
		}
	};
}

function plugin2() {
	return {
		name: 'plugin2',
		onLog(level, log) {
			if (log.plugin === 'plugin1' && log.pluginCode === 'SPECIAL_CODE') {
				// You can modify logs in this hooks as well
				log.meta = 'processed by plugin 2';
				// This turns the log back to "info". If this happens in
				// response to the first plugin, it will not be passed back to
				// either plugin to avoid an infinite loop. If both plugins are
				// active, the log will be an info log if the second plugin is
				// placed after the first one
				this.info(log);
				return false;
			}
		}
	};
}

options 钩子一样,此钩子无法访问大多数 插件上下文 实用程序函数,因为它可能在 Rollup 完全配置之前运行。 唯一支持的属性是 this.meta 以及用于日志记录和错误的 this.errorthis.warnthis.infothis.debug

Like the options hook, this hook does not have access to most plugin context utility functions as it may be run before Rollup is fully configured. The only supported properties are this.meta as well as this.error, this.warn, this.info and this.debug for logging and errors.

options

类型:(options: InputOptions) => InputOptions | null
种类:异步、顺序
以前的:这是构建阶段的第一个钩子
下一个:buildStart

替换或操作传递给 rollup.rollup 的选项对象。 返回 null 不会替代任何内容。 如果你只需要读取选项,建议使用 buildStart 钩子,因为在考虑所有 options 钩子的转换后,该钩子可以访问选项。

Replaces or manipulates the options object passed to rollup.rollup. Returning null does not replace anything. If you just need to read the options, it is recommended to use the buildStart hook as that hook has access to the options after the transformations from all options hooks have been taken into account.

onLog 钩子一样,该钩子无法访问大多数 插件上下文 实用程序函数,因为它是在 Rollup 完全配置之前运行的。 唯一支持的属性是 this.meta 以及用于日志记录和错误的 this.errorthis.warnthis.infothis.debug

Like the onLog hook, this hook does not have access to most plugin context utility functions as it is run before Rollup is fully configured. The only supported properties are this.meta as well as this.error, this.warn, this.info and this.debug for logging and errors.

resolveDynamicImport

类型:ResolveDynamicImportHook
种类:异步,首先
以前的:moduleParsed 为导入文件
下一个:如果钩子使用尚未加载的 id 进行解析,则为 load;如果动态导入包含字符串并且钩子未解析,则为 resolveId;否则为 buildEnd
typescript
type ResolveDynamicImportHook = (
	specifier: string | AstNode,
	importer: string,
	options: { attributes: Record<string, string> }
) => ResolveIdResult;
type ResolveDynamicImportHook = (
	specifier: string | AstNode,
	importer: string,
	options: { attributes: Record<string, string> }
) => ResolveIdResult;

提示

解析结果 的返回类型与 resolveId 钩子的返回类型相同。

The return type ResolveIdResult is the same as that of the resolveId hook.

定义动态导入的自定义解析器。 返回 false 表示导入应保持原样,而不是传递给其他解析器,从而使其成为外部的。 与 resolveId 钩子类似,你也可以返回一个对象来解析导入到不同的 id,同时将其标记为外部。

Defines a custom resolver for dynamic imports. Returning false signals that the import should be kept as it is and not be passed to other resolvers thus making it external. Similar to the resolveId hook, you can also return an object to resolve the import to a different id while marking it as external at the same time.

attributes 告诉你导入中存在哪些导入属性。 IE。 import("foo", {assert: {type: "json"}}) 将传递 attributes: {type: "json"}

attributes tells you which import attributes were present in the import. I.e. import("foo", {assert: {type: "json"}}) will pass along attributes: {type: "json"}.

如果动态导入传递一个字符串作为参数,则从此钩子返回的字符串将被解释为现有模块 id,同时返回 null 将遵循其他解析器并最终遵循 resolveId

In case a dynamic import is passed a string as argument, a string returned from this hook will be interpreted as an existing module id while returning null will defer to other resolvers and eventually to resolveId .

如果动态导入未传递字符串作为参数,则此钩子可以访问原始 AST 节点进行分析,并且在以下方面表现略有不同:

In case a dynamic import is not passed a string as argument, this hook gets access to the raw AST nodes to analyze and behaves slightly different in the following ways:

  • 如果所有插件都返回 null,则导入将被视为 external,且不会触发警告。
  • 如果返回一个字符串,则该字符串不会解释为模块 ID,而是用作导入参数的替换。 插件有责任确保生成的代码有效。
  • 要解决对现有模块的此类导入,你仍然可以返回对象 {id, external}

注意这个 hook 的返回值之后不会传递给 resolveId; 如果你需要访问静态解析算法,你可以在插件上下文中使用 this.resolve(source, importer)

Note that the return value of this hook will not be passed to resolveId afterwards; if you need access to the static resolution algorithm, you can use this.resolve(source, importer) on the plugin context.

resolveId

类型:ResolveIdHook
种类:异步,首先
以前的:如果我们正在解析入口点,则为 buildStart;如果我们正在解析导入,则为 moduleParsed;或者作为 resolveDynamicImport 的后备。 此外,可以在插件钩子的构建阶段通过调用 this.emitFile 触发入口点或随时通过调用 this.resolve 手动解析 id 来触发此钩子
下一个:如果解析的 id 尚未加载,则为 load,否则为 buildEnd
typescript
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;
}
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;
}

定义自定义解析器。 解析器可用于例如 定位第三方依赖。 这里 source 是导入者,与导入语句中所写的完全相同,即 for

Defines a custom resolver. A resolver can be useful for e.g. locating third-party dependencies. Here source is the importee exactly as it is written in the import statement, i.e. for

js
import { foo } from '../bar.js';
import { foo } from '../bar.js';

源将是 "../bar.js"

the source will be "../bar.js".

importer 是导入模块的完全解析 ID。 解析入口点时,导入商通常是 undefined。 这里的一个例外是通过 this.emitFile 生成的入口点,你可以提供 importer 参数。

The importer is the fully resolved id of the importing module. When resolving entry points, importer will usually be undefined. An exception here are entry points generated via this.emitFile as here, you can provide an importer argument.

对于这些情况,isEntry 选项将告诉你我们是否正在解析用户定义的入口点、触发的块,或者是否为 this.resolve 上下文函数提供了 isEntry 参数。

For those cases, the isEntry option will tell you if we are resolving a user defined entry point, an emitted chunk, or if the isEntry parameter was provided for the this.resolve context function.

例如,你可以使用它作为为入口点定义自定义代理模块的机制。 以下插件将代理所有入口点以注入 polyfill 导入。

You can use this for instance as a mechanism to define custom proxy modules for entry points. The following plugin will proxy all entry points to inject a polyfill import.

js
// We prefix the polyfill id with \0 to tell other plugins not to try to load or
// transform it
const POLYFILL_ID = '\0polyfill';
const PROXY_SUFFIX = '?inject-polyfill-proxy';

function injectPolyfillPlugin() {
	return {
		name: 'inject-polyfill',
		async resolveId(source, importer, options) {
			if (source === POLYFILL_ID) {
				// It is important that side effects are always respected
				// for polyfills, otherwise using
				// "treeshake.moduleSideEffects: false" may prevent the
				// polyfill from being included.
				return { id: POLYFILL_ID, moduleSideEffects: true };
			}
			if (options.isEntry) {
				// Determine what the actual entry would have been.
				const resolution = await this.resolve(source, importer, options);
				// If it cannot be resolved or is external, just return it
				// so that Rollup can display an error
				if (!resolution || resolution.external) return resolution;
				// In the load hook of the proxy, we need to know if the
				// entry has a default export. There, however, we no longer
				// have the full "resolution" object that may contain
				// meta-data from other plugins that is only added on first
				// load. Therefore we trigger loading here.
				const moduleInfo = await this.load(resolution);
				// We need to make sure side effects in the original entry
				// point are respected even for
				// treeshake.moduleSideEffects: false. "moduleSideEffects"
				// is a writable property on ModuleInfo.
				moduleInfo.moduleSideEffects = true;
				// It is important that the new entry does not start with
				// \0 and has the same directory as the original one to not
				// mess up relative external import generation. Also
				// keeping the name and just adding a "?query" to the end
				// ensures that preserveModules will generate the original
				// entry name for this entry.
				return `${resolution.id}${PROXY_SUFFIX}`;
			}
			return null;
		},
		load(id) {
			if (id === POLYFILL_ID) {
				// Replace with actual polyfill
				return "console.log('polyfill');";
			}
			if (id.endsWith(PROXY_SUFFIX)) {
				const entryId = id.slice(0, -PROXY_SUFFIX.length);
				// We know ModuleInfo.hasDefaultExport is reliable because
				// we awaited this.load in resolveId
				const { hasDefaultExport } = this.getModuleInfo(entryId);
				let code =
					`import ${JSON.stringify(POLYFILL_ID)};` +
					`export * from ${JSON.stringify(entryId)};`;
				// Namespace reexports do not reexport default, so we need
				// special handling here
				if (hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(entryId)};`;
				}
				return code;
			}
			return null;
		}
	};
}
// We prefix the polyfill id with \0 to tell other plugins not to try to load or
// transform it
const POLYFILL_ID = '\0polyfill';
const PROXY_SUFFIX = '?inject-polyfill-proxy';

function injectPolyfillPlugin() {
	return {
		name: 'inject-polyfill',
		async resolveId(source, importer, options) {
			if (source === POLYFILL_ID) {
				// It is important that side effects are always respected
				// for polyfills, otherwise using
				// "treeshake.moduleSideEffects: false" may prevent the
				// polyfill from being included.
				return { id: POLYFILL_ID, moduleSideEffects: true };
			}
			if (options.isEntry) {
				// Determine what the actual entry would have been.
				const resolution = await this.resolve(source, importer, options);
				// If it cannot be resolved or is external, just return it
				// so that Rollup can display an error
				if (!resolution || resolution.external) return resolution;
				// In the load hook of the proxy, we need to know if the
				// entry has a default export. There, however, we no longer
				// have the full "resolution" object that may contain
				// meta-data from other plugins that is only added on first
				// load. Therefore we trigger loading here.
				const moduleInfo = await this.load(resolution);
				// We need to make sure side effects in the original entry
				// point are respected even for
				// treeshake.moduleSideEffects: false. "moduleSideEffects"
				// is a writable property on ModuleInfo.
				moduleInfo.moduleSideEffects = true;
				// It is important that the new entry does not start with
				// \0 and has the same directory as the original one to not
				// mess up relative external import generation. Also
				// keeping the name and just adding a "?query" to the end
				// ensures that preserveModules will generate the original
				// entry name for this entry.
				return `${resolution.id}${PROXY_SUFFIX}`;
			}
			return null;
		},
		load(id) {
			if (id === POLYFILL_ID) {
				// Replace with actual polyfill
				return "console.log('polyfill');";
			}
			if (id.endsWith(PROXY_SUFFIX)) {
				const entryId = id.slice(0, -PROXY_SUFFIX.length);
				// We know ModuleInfo.hasDefaultExport is reliable because
				// we awaited this.load in resolveId
				const { hasDefaultExport } = this.getModuleInfo(entryId);
				let code =
					`import ${JSON.stringify(POLYFILL_ID)};` +
					`export * from ${JSON.stringify(entryId)};`;
				// Namespace reexports do not reexport default, so we need
				// special handling here
				if (hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(entryId)};`;
				}
				return code;
			}
			return null;
		}
	};
}

attributes 告诉你导入中存在哪些导入属性。 IE。 import "foo" assert {type: "json"} 将传递 attributes: {type: "json"}

attributes tells you which import attributes were present in the import. I.e. import "foo" assert {type: "json"} will pass along attributes: {type: "json"}.

返回 null 会遵循其他 resolveId 函数,并最终遵循默认的解析行为。 返回 false 表示 source 应被视为外部模块并且不包含在打包包中。 如果相对导入发生这种情况,则 id 将按照与使用 external 选项时相同的方式重新规范化。

Returning null defers to other resolveId functions and eventually the default resolution behavior. Returning false signals that source should be treated as an external module and not included in the bundle. If this happens for a relative import, the id will be renormalized the same way as when the external option is used.

如果返回一个对象,则可以解析对不同 id 的导入,同时将其从包中排除。 这允许你用外部依赖替换依赖,而无需用户通过 external 选项手动将它们标记为 "external":

If you return an object, then it is possible to resolve an import to a different id while excluding it from the bundle at the same time. This allows you to replace dependencies with external dependencies without the need for the user to mark them as "external" manually via the external option:

js
function externalizeDependencyPlugin() {
	return {
		name: 'externalize-dependency',
		resolveId(source) {
			if (source === 'my-dependency') {
				return { id: 'my-dependency-develop', external: true };
			}
			return null;
		}
	};
}
function externalizeDependencyPlugin() {
	return {
		name: 'externalize-dependency',
		resolveId(source) {
			if (source === 'my-dependency') {
				return { id: 'my-dependency-develop', external: true };
			}
			return null;
		}
	};
}

如果 externaltrue,则绝对 id 将根据用户对 makeAbsoluteExternalsRelative 选项的选择转换为相对 id。 可以通过传递 external: "relative" 来始终将绝对 id 转换为相对 id 或传递 external: "absolute" 来将其保留为绝对 id 来覆盖此选择。 返回对象时,相对外部 id(即以 ./../ 开头的 id)不会在内部转换为绝对 id 并在输出中转换回相对 id,而是原封不动地包含在输出中。 如果你希望重新规范化相对 ID 并进行数据去重,请将绝对文件系统位置返回为 id 并选择 external: "relative"

If external is true, then absolute ids will be converted to relative ids based on the user's choice for the makeAbsoluteExternalsRelative option. This choice can be overridden by passing either external: "relative" to always convert an absolute id to a relative id or external: "absolute" to keep it as an absolute id. When returning an object, relative external ids, i.e. ids starting with ./ or ../, will not be internally converted to an absolute id and converted back to a relative id in the output, but are instead included in the output unchanged. If you want relative ids to be renormalised and deduplicated instead, return an absolute file system location as id and choose external: "relative".

如果在解析模块 ID 的第一个钩子中为 moduleSideEffects 返回 false,并且没有其他模块从该模块导入任何内容,则即使该模块有副作用,也不会包含该模块。 如果返回 true,Rollup 将使用其默认算法来包含模块中具有副作用(例如修改全局或导出变量)的所有语句。 如果返回 "no-treeshake",则该模块的 treeshaking 将被关闭,并且它也将包含在生成的块之一中,即使它是空的。 如果返回 null 或省略该标志,则 moduleSideEffects 将由 treeshake.moduleSideEffects 选项确定或默认为 trueloadtransform 钩子可以覆盖它。

If false is returned for moduleSideEffects in the first hook that resolves a module id and no other module imports anything from this module, then this module will not be included even if the module would have side effects. If true is returned, Rollup will use its default algorithm to include all statements in the module that have side effects (such as modifying a global or exported variable). If "no-treeshake" is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty. If null is returned or the flag is omitted, then moduleSideEffects will be determined by the treeshake.moduleSideEffects option or default to true. The load and transform hooks can override this.

resolvedBy 可以在返回的对象中显式声明。 它将替换 this.resolve 返回的相应字段。

resolvedBy can be explicitly declared in the returned object. It will replace the corresponding field returned by this.resolve.

如果你为外部模块返回 attributes 的值,这将确定在生成 "es" 输出时如何渲染该模块的导入。 例如。 {id: "foo", external: true, attributes: {type: "json"}} 将导致该模块的导入显示为 import "foo" assert {type: "json"}。 如果不传递值,则将使用 attributes 输入参数的值。 传递一个空对象以删除任何属性。 虽然 attributes 不影响打包模块的渲染,但它们仍然需要在模块的所有导入中保持一致,否则会触发警告。 loadtransform 钩子可以覆盖它。

If you return a value for attributes for an external module, this will determine how imports of this module will be rendered when generating "es" output. E.g. {id: "foo", external: true, attributes: {type: "json"}} will cause imports of this module appear as import "foo" assert {type: "json"}. If you do not pass a value, the value of the attributes input parameter will be used. Pass an empty object to remove any attributes. While attributes do not influence rendering for bundled modules, they still need to be consistent across all imports of a module, otherwise a warning is emitted. The load and transform hooks can override this.

请参阅 合成命名导出 了解 syntheticNamedExports 选项的效果。 如果返回 null 或省略该标志,则 syntheticNamedExports 将默认为 falseloadtransform 钩子可以覆盖它。

See synthetic named exports for the effect of the syntheticNamedExports option. If null is returned or the flag is omitted, then syntheticNamedExports will default to false. The load and transform hooks can override this.

请参阅 自定义模块元数据 了解如何使用 meta 选项。 如果返回 null 或省略该选项,则 meta 将默认为空对象。 loadtransform 钩子可以添加或替换该对象的属性。

See custom module meta-data for how to use the meta option. If null is returned or the option is omitted, then meta will default to an empty object. The load and transform hooks can add or replace properties of this object.

请注意,虽然每次导入模块时都会调用 resolveId,因此可以多次解析为相同的 id,但 externalattributesmetamoduleSideEffectssyntheticNamedExports 的值只能在加载模块之前设置一次。 原因是,在此调用之后,Rollup 将继续使用该模块的 loadtransform 钩子,这些钩子可能会覆盖这些值,并且如果它们这样做,则应优先考虑。

Note that while resolveId will be called for each import of a module and can therefore resolve to the same id many times, values for external, attributes, meta, moduleSideEffects or syntheticNamedExports can only be set once before the module is loaded. The reason is that after this call, Rollup will continue with the load and transform hooks for that module that may override these values and should take precedence if they do so.

当通过 this.resolve 从插件触发此钩子时,可以将自定义选项对象传递给此钩子。 虽然此对象将未经修改地传递,但插件应遵循向对象添加 custom 属性的约定,其中键对应于选项所针对的插件的名称。 详情参见 自定义解析器选项

When triggering this hook from a plugin via this.resolve, it is possible to pass a custom options object to this hook. While this object will be passed unmodified, plugins should follow the convention of adding a custom property with an object where the keys correspond to the names of the plugins that the options are intended for. For details see custom resolver options.

在监视模式下或显式使用缓存时,缓存模块的已解析导入也将从缓存中获取,并且不再通过 resolveId 钩子确定。 为了防止这种情况,你可以从该模块的 shouldTransformCachedModule 钩子返回 true。 这将从缓存中删除模块及其导入解析并再次调用 transformresolveId

In watch mode or when using the cache explicitly, the resolved imports of a cached module are also taken from the cache and not determined via the resolveId hook again. To prevent this, you can return true from the shouldTransformCachedModule hook for that module. This will remove the module and its import resolutions from cache and call transform and resolveId again.

shouldTransformCachedModule

类型:ShouldTransformCachedModuleHook
种类:异步,首先
以前的:load 加载缓存文件以将其代码与缓存版本进行比较
下一个:如果没有插件则返回 moduleParsed,否则返回 transform
typescript
type ShouldTransformCachedModuleHook = (options: {
	ast: AstNode;
	code: string;
	id: string;
	meta: { [plugin: string]: any };
	moduleSideEffects: boolean | 'no-treeshake';
	syntheticNamedExports: boolean | string;
}) => boolean | NullValue;
type ShouldTransformCachedModuleHook = (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

If the Rollup cache is used (e.g. in watch mode or explicitly via the JavaScript API), Rollup will skip the transform hook of a module if after the load hook, the loaded code is identical to the code of the cached copy. To prevent this, discard the cached copy and instead transform a module, plugins can implement this hook and return true.

该钩子还可用于查找缓存了哪些模块并访问其缓存的元信息。

This hook can also be used to find out which modules were cached and access their cached meta information.

如果插件不返回布尔值,Rollup 将为其他插件触发此钩子,否则将跳过所有剩余插件。

If a plugin does not return a boolean, Rollup will trigger this hook for other plugins, otherwise all remaining plugins will be skipped.

transform

类型:(code: string, id: string) => TransformResult
种类:异步、顺序
以前的:load 当前处理的文件被加载到的位置。 如果使用缓存并且存在该模块的缓存副本,则 shouldTransformCachedModule 如果插件为该钩子返回 true
下一个:文件处理和解析后 moduleParsed
typescript
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;
}
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;
}

可用于改造各个模块。 为了防止额外的解析开销,例如 由于某种原因,该钩子已使用 this.parse 生成 AST,该钩子可以选择返回 { code, ast, map } 对象。 ast 必须是标准 ESTree AST,每个节点具有 startend 属性。 如果转换不移动代码,你可以通过将 map 设置为 null 来保留现有源映射。 否则,你可能需要生成源映射。 参见 源代码转换部分

Can be used to transform individual modules. To prevent additional parsing overhead in case e.g. this hook already used this.parse to generate an AST for some reason, this hook can optionally return a { code, ast, map } object. The ast must be a standard ESTree AST with start and end properties for each node. If the transformation does not move code, you can preserve existing sourcemaps by setting map to null. Otherwise, you might need to generate the source map. See the section on source code transformations.

请注意,在监视模式下或显式使用缓存时,此钩子的结果在重建时会被缓存,并且仅当模块 code 已更改或通过添加的文件已更改时,才会再次为模块 id 触发该钩子 this.addWatchFile 该模块上次触发钩子的时间。

Note that in watch mode or when using the cache explicitly, the result of this hook is cached when rebuilding and the hook is only triggered again for a module id if either the code of the module has changed or a file has changed that was added via this.addWatchFile the last time the hook was triggered for this module.

在所有其他情况下,都会触发 shouldTransformCachedModule 钩子,从而可以访问缓存的模块。 从 shouldTransformCachedModule 返回 true 将从缓存中删除该模块,并再次调用 transform

In all other cases, the shouldTransformCachedModule hook is triggered instead, which gives access to the cached module. Returning true from shouldTransformCachedModule will remove the module from cache and instead call transform again.

你还可以使用返回值的对象形式来配置模块的其他属性。 请注意,可以仅返回属性而不返回代码转换。

You can also use the object form of the return value to configure additional properties of the module. Note that it's possible to return only properties and no code transformations.

如果为 moduleSideEffects 返回 false 并且没有其他模块从该模块导入任何内容,则即使该模块会产生副作用,也不会包含该模块。

If false is returned for moduleSideEffects and no other module imports anything from this module, then this module will not be included even if the module would have side effects.

如果返回 true,Rollup 将使用其默认算法来包含模块中具有副作用(例如修改全局或导出变量)的所有语句。

If true is returned, Rollup will use its default algorithm to include all statements in the module that have side effects (such as modifying a global or exported variable).

如果返回 "no-treeshake",则该模块的 treeshaking 将被关闭,并且它也将包含在生成的块之一中,即使它是空的。

If "no-treeshake" is returned, treeshaking will be turned off for this module and it will also be included in one of the generated chunks even if it is empty.

如果返回 null 或省略该标志,则 moduleSideEffects 将由加载此模块的 load 钩子、解析此模块的第一个 resolveId 钩子、treeshake.moduleSideEffects 选项或最终默认为 true 确定。

If null is returned or the flag is omitted, then moduleSideEffects will be determined by the load hook that loaded this module, the first resolveId hook that resolved this module, the treeshake.moduleSideEffects option, or eventually default to true.

attributes 包含导入此模块时使用的导入属性。 目前,它们不影响打包模块的渲染,而是用于文档目的。 如果返回 null 或省略该标志,则 attributes 将由加载此模块的 load 钩子、解析此模块的第一个 resolveId 钩子或此模块的第一次导入中存在的属性确定。

attributes contain the import attributes that were used when this module was imported. At the moment, they do not influence rendering for bundled modules but rather serve documentation purposes. If null is returned or the flag is omitted, then attributes will be determined by the load hook that loaded this module, the first resolveId hook that resolved this module, or the attributes present in the first import of this module.

请参阅 合成命名导出 了解 syntheticNamedExports 选项的效果。 如果返回 null 或省略该标志,则 syntheticNamedExports 将由加载此模块的 load 钩子、解析此模块的第一个 resolveId 钩子、treeshake.moduleSideEffects 选项或最终默认为 false 确定。

See synthetic named exports for the effect of the syntheticNamedExports option. If null is returned or the flag is omitted, then syntheticNamedExports will be determined by the load hook that loaded this module, the first resolveId hook that resolved this module, the treeshake.moduleSideEffects option, or eventually default to false.

请参阅 自定义模块元数据 了解如何使用 meta 选项。 如果返回 null 或省略该选项,则 meta 将由加载此模块的 load 钩子、解析此模块的第一个 resolveId 钩子确定,或者最终默认为空对象。

See custom module meta-data for how to use the meta option. If null is returned or the option is omitted, then meta will be determined by the load hook that loaded this module, the first resolveId hook that resolved this module or eventually default to an empty object.

你可以使用 this.getModuleInfo 找出该钩子中 attributesmetamoduleSideEffectssyntheticNamedExports 的先前值。

You can use this.getModuleInfo to find out the previous values of attributes, meta, moduleSideEffects and syntheticNamedExports inside this hook.

watchChange

类型:watchChange: (id: string, change: {event: 'create' | 'update' | 'delete'}) => void
种类:异步、并行
上一页/下一页:在构建和输出生成阶段,可以随时触发该钩子。 如果是这种情况,当前构建仍将继续,但一旦当前构建完成,将计划开始新的构建,并从 options 重新开始

每当 Rollup 在 --watch 模式下检测到受监控文件发生更改时通知插件。 如果返回 Promise,Rollup 将等待 Promise 解析后再安排另一个构建。 输出插件不能使用此钩子。 第二个参数包含更改事件的其他详细信息。

Notifies a plugin whenever Rollup has detected a change to a monitored file in --watch mode. If a Promise is returned, Rollup will wait for the Promise to resolve before scheduling another build. This hook cannot be used by output plugins. The second argument contains additional details of the change event.

输出生成钩子

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

Output generation hooks can provide information about a generated bundle and modify a build once complete. They work the same way and have the same types as Build Hooks but are called separately for each call to bundle.generate(outputOptions) or bundle.write(outputOptions). Plugins that only use output generation hooks can also be passed in via the output options and therefore run only for certain outputs.

输出生成阶段的第一个钩子是 outputOptions,最后一个钩子是 generateBundle(如果通过 bundle.generate(...) 成功生成输出)、writeBundle(如果通过 bundle.write(...) 成功生成输出)或 renderError(如果在输出生成期间的任何时间发生错误)。

The first hook of the output generation phase is outputOptions, the last one is either generateBundle if the output was successfully generated via bundle.generate(...), writeBundle if the output was successfully generated via bundle.write(...), or renderError if an error occurred at any time during the output generation.

parallel
sequential
first
async
sync

each chunk
each chunk
next chunk
each import()
each import.meta.*
import.meta.url
other
next chunk
banner
footer
intro
outro
renderDynamicImport
resolveFileUrl
resolveImportMeta
augmentChunkHash
closeBundle
generateBundle
outputOptions
renderChunk
renderError
renderStart
writeBundle

此外,closeBundle 可以作为最后一个钩子调用,但用户有责任手动调用 bundle.close() 来触发此钩子。 CLI 将始终确保情况确实如此。

Additionally, closeBundle can be called as the very last hook, but it is the responsibility of the User to manually call bundle.close() to trigger this. The CLI will always make sure this is the case.

augmentChunkHash

类型:(chunkInfo: ChunkInfo) => string
种类:同步、顺序
以前的:renderChunk
下一个:如果还有其他块还需要处理则 renderChunk,否则 generateBundle

可用于增强各个块的哈希值。 为每个 Rollup 输出块调用。 返回假值不会修改哈希值。 真值将传递给 hash.updatechunkInfogenerateBundle 中的简化版本,没有 codemap,并在文件名中使用散列占位符。

Can be used to augment the hash of individual chunks. Called for each Rollup output chunk. Returning a falsy value will not modify the hash. Truthy values will be passed to hash.update. The chunkInfo is a reduced version of the one in generateBundle without code and map and using placeholders for hashes in file names.

以下插件将使带有当前时间戳的块 foo 的哈希值无效:

The following plugin will invalidate the hash of chunk foo with the current timestamp:

js
function augmentWithDatePlugin() {
	return {
		name: 'augment-with-date',
		augmentChunkHash(chunkInfo) {
			if (chunkInfo.name === 'foo') {
				return Date.now().toString();
			}
		}
	};
}
function augmentWithDatePlugin() {
	return {
		name: 'augment-with-date',
		augmentChunkHash(chunkInfo) {
			if (chunkInfo.name === 'foo') {
				return Date.now().toString();
			}
		}
	};
}
类型:string | ((chunk: ChunkInfo) => string)
种类:异步、顺序
以前的:resolveFileUrl 用于每次使用 import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta 用于当前块中对 import.meta 的所有其他访问
下一个:renderDynamicImport 代表下一个块中的每个动态导入表达式(如果有另一个动态导入表达式),否则 renderChunk 代表第一个块

比照。 output.banner/output.footer

Cf. output.banner/output.footer.

closeBundle

类型:closeBundle: () => Promise<void> | void
种类:异步、并行
以前的:buildEnd 如果存在构建错误,否则当调用 bundle.close() 时,在这种情况下这将是最后一个触发的钩子

可用于清理可能正在运行的任何外部服务。 Rollup 的 CLI 将确保每次运行后都会调用此钩子,但 JavaScript API 用户有责任在生成打包包后手动调用 bundle.close()。 因此,任何依赖此功能的插件都应该在其文档中仔细提及这一点。

Can be used to clean up any external service that may be running. Rollup's CLI will make sure this hook is called after each run, but it is the responsibility of users of the JavaScript API to manually call bundle.close() once they are done generating bundles. For that reason, any plugin relying on this feature should carefully mention this in its documentation.

如果插件想要在监视模式下跨构建保留资源,他们可以检查此钩子中的 this.meta.watchMode 并在 closeWatcher 中对监视模式执行必要的清理。

If a plugin wants to retain resources across builds in watch mode, they can check for this.meta.watchMode in this hook and perform the necessary cleanup for watch mode in closeWatcher.

类型:string | ((chunk: ChunkInfo) => string)
种类:异步、顺序
以前的:resolveFileUrl 用于每次使用 import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta 用于当前块中对 import.meta 的所有其他访问
下一个:renderDynamicImport 代表下一个块中的每个动态导入表达式(如果有另一个动态导入表达式),否则 renderChunk 代表第一个块

比照。 output.banner/output.footer

Cf. output.banner/output.footer.

generateBundle

类型:(options: OutputOptions, bundle: { [fileName: string]: OutputAsset | OutputChunk }, isWrite: boolean) => void
种类:异步、顺序
以前的:augmentChunkHash
下一个:如果输出是通过 bundle.write(...) 生成的,则为 writeBundle,否则这是输出生成阶段的最后一个钩子,如果生成另一个输出,则可能会再次跟随 outputOptions
typescript
interface OutputAsset {
	fileName: string;
	name?: string;
	needsCodeReference: boolean;
	source: string | Uint8Array;
	type: 'asset';
}

interface OutputChunk {
	code: string;
	dynamicImports: string[];
	exports: string[];
	facadeModuleId: string | null;
	fileName: string;
	implicitlyLoadedBefore: string[];
	imports: string[];
	importedBindings: { [imported: string]: string[] };
	isDynamicEntry: boolean;
	isEntry: boolean;
	isImplicitEntry: boolean;
	map: SourceMap | null;
	modules: {
		[id: string]: {
			renderedExports: string[];
			removedExports: string[];
			renderedLength: number;
			originalLength: number;
			code: string | null;
		};
	};
	moduleIds: string[];
	name: string;
	preliminaryFileName: string;
	referencedFiles: string[];
	sourcemapFileName: string | null;
	type: 'chunk';
}
interface OutputAsset {
	fileName: string;
	name?: string;
	needsCodeReference: boolean;
	source: string | Uint8Array;
	type: 'asset';
}

interface OutputChunk {
	code: string;
	dynamicImports: string[];
	exports: string[];
	facadeModuleId: string | null;
	fileName: string;
	implicitlyLoadedBefore: string[];
	imports: string[];
	importedBindings: { [imported: string]: string[] };
	isDynamicEntry: boolean;
	isEntry: boolean;
	isImplicitEntry: boolean;
	map: SourceMap | null;
	modules: {
		[id: string]: {
			renderedExports: string[];
			removedExports: string[];
			renderedLength: number;
			originalLength: number;
			code: string | null;
		};
	};
	moduleIds: string[];
	name: string;
	preliminaryFileName: string;
	referencedFiles: string[];
	sourcemapFileName: string | null;
	type: 'chunk';
}

bundle.generate() 结束时或在 bundle.write() 写入文件之前调用。 要在写入文件后对其进行修改,请使用 writeBundle 钩子。 bundle 提供了正在写入或生成的文件的完整列表及其详细信息。

Called at the end of bundle.generate() or immediately before the files are written in bundle.write(). To modify the files after they have been written, use the writeBundle hook. bundle provides the full list of files being written or generated along with their details.

你可以通过从此钩子中的打包对象中删除文件来防止触发文件。 要触发其他文件,请使用 this.emitFile 插件上下文函数。

You can prevent files from being emitted by deleting them from the bundle object in this hook. To emit additional files, use the this.emitFile plugin context function.

危险

不要直接将资源添加到打包包中。 这绕过了 Rollup 用于跟踪资源的内部机制。 它还可能导致你的资源遗漏 Rollup 内部依赖的重要属性,并且你的插件可能会因较小的 Rollup 版本而中断。

Do not directly add assets to the bundle. This circumvents internal mechanisms that Rollup has for tracking assets. It can also cause your asset to miss vital properties that Rollup relies on internally and your plugin can break with minor Rollup releases.

相反,请始终使用 this.emitFile

Instead, always use this.emitFile.

intro

类型:string | ((chunk: ChunkInfo) => string)
种类:异步、顺序
以前的:resolveFileUrl 用于每次使用 import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta 用于当前块中对 import.meta 的所有其他访问
下一个:renderDynamicImport 代表下一个块中的每个动态导入表达式(如果有另一个动态导入表达式),否则 renderChunk 代表第一个块

比照。 output.intro/output.outro

Cf. output.intro/output.outro.

outputOptions

类型:(outputOptions: OutputOptions) => OutputOptions | null
种类:同步、顺序
以前的:如果这是第一次生成输出,则为 buildEnd,否则为 generateBundlewriteBundlerenderError,具体取决于先前生成的输出。 这是输出生成阶段的第一个钩子
下一个:renderStart

替换或操作传递给 bundle.generate()bundle.write() 的输出选项对象。 返回 null 不会替代任何内容。 如果你只需要读取输出选项,建议使用 renderStart 钩子,因为在考虑所有 outputOptions 钩子的转换后,该钩子可以访问输出选项。

Replaces or manipulates the output options object passed to bundle.generate() or bundle.write(). Returning null does not replace anything. If you just need to read the output options, it is recommended to use the renderStart hook as this hook has access to the output options after the transformations from all outputOptions hooks have been taken into account.

outro

类型:string | ((chunk: ChunkInfo) => string)
种类:异步、顺序
以前的:resolveFileUrl 用于每次使用 import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta 用于当前块中对 import.meta 的所有其他访问
下一个:renderDynamicImport 代表下一个块中的每个动态导入表达式(如果有另一个动态导入表达式),否则 renderChunk 代表第一个块

比照。 output.intro/output.outro

Cf. output.intro/output.outro.

renderChunk

类型:RenderChunkHook
种类:异步、顺序
以前的:最后一个块的 bannerfooterintrooutro
下一个:augmentChunkHash
typescript
type RenderChunkHook = (
	code: string,
	chunk: RenderedChunk,
	options: NormalizedOutputOptions,
	meta: { chunks: Record<string, RenderedChunk> }
) => { code: string; map?: SourceMapInput } | string | null;
type RenderChunkHook = (
	code: string,
	chunk: RenderedChunk,
	options: NormalizedOutputOptions,
	meta: { chunks: Record<string, RenderedChunk> }
) => { code: string; map?: SourceMapInput } | string | null;

可用于转换各个块。 为每个 Rollup 输出块文件调用。 返回 null 将不应用任何转换。 如果你更改此钩子中的代码并希望支持源映射,则需要返回描述你的更改的 map,请参阅 源代码转换部分

Can be used to transform individual chunks. Called for each Rollup output chunk file. Returning null will apply no transformations. If you change code in this hook and want to support source maps, you need to return a map describing your changes, see the section on source code transformations.

chunk 包含有关使用与 generateBundle 钩子相同的 ChunkInfo 类型的块的附加信息,但存在以下差异:

chunk contains additional information about the chunk using the same ChunkInfo type as the generateBundle hook with the following differences:

  • codemap 未设置。 相反,请使用此钩子的 code 参数。
  • 所有包含哈希值的引用块文件名将包含哈希占位符。 这包括 fileNameimportsimportedBindingsdynamicImportsimplicitlyLoadedBefore。 当你在此钩子返回的代码中使用此类占位符文件名或其中的一部分时,Rollup 会将占位符替换为 generateBundle 之前的实际哈希值,确保哈希值反映最终生成的块的实际内容,包括所有引用的文件哈希值 。

chunk 是可变的,在此钩子中应用的更改将传播到其他插件和生成的包。 这意味着如果你在此钩子中添加或删除导入或导出,则应该更新 importsimportedBindings 和/或 exports

chunk is mutable and changes applied in this hook will propagate to other plugins and to the generated bundle. That means if you add or remove imports or exports in this hook, you should update imports, importedBindings and/or exports.

meta.chunks 包含有关 Rollup 生成的所有块的信息,并让你可以访问它们的 ChunkInfo,同样使用哈希值占位符。 这意味着你可以在这个钩子中探索整个块图。

meta.chunks contains information about all the chunks Rollup is generating and gives you access to their ChunkInfo, again using placeholders for hashes. That means you can explore the entire chunk graph in this hook.

renderDynamicImport

类型:renderDynamicImportHook
种类:同步,首先
以前的:如果这是第一个块,则为 renderStart,否则为前一个块的 bannerfooterintrooutro
下一个:resolveFileUrl 用于每次使用 import.meta.ROLLUP_FILE_URL_referenceIdresolveImportMeta 用于当前块中对 import.meta 的所有其他访问
typescript
type renderDynamicImportHook = (options: {
	customResolution: string | null;
	format: string;
	moduleId: string;
	targetModuleId: string | null;
}) => { left: string; right: string } | null;
type renderDynamicImportHook = (options: {
	customResolution: string | null;
	format: string;
	moduleId: string;
	targetModuleId: string | null;
}) => { left: string; right: string } | null;

此钩子通过提供导入表达式参数左侧 (import() 和右侧 ()) 的代码替换,提供对如何渲染动态导入的细粒度控制。 返回 null 会遵循此类型的其他钩子,并最终渲染特定于格式的默认值。

This hook provides fine-grained control over how dynamic imports are rendered by providing replacements for the code to the left (import() and right ()) of the argument of the import expression. Returning null defers to other hooks of this type and ultimately renders a format-specific default.

format 是渲染的输出格式,moduleId 是执行动态导入的模块的 ID。 如果导入可以解析为内部或外部 id,则 targetModuleId 将设置为该 id,否则将为 null。 如果动态导入包含由 resolveDynamicImport 钩子解析为替换字符串的非字符串表达式,则 customResolution 将包含该字符串。

format is the rendered output format, moduleId the id of the module performing the dynamic import. If the import could be resolved to an internal or external id, then targetModuleId will be set to this id, otherwise it will be null. If the dynamic import contained a non-string expression that was resolved by a resolveDynamicImport hook to a replacement string, then customResolution will contain that string.

以下代码将使用自定义处理程序替换所有动态导入,添加 import.meta.url 作为第二个参数以允许处理程序正确解析相对导入:

The following code will replace all dynamic imports with a custom handler, adding import.meta.url as a second argument to allow the handler to resolve relative imports correctly:

js
function dynamicImportPolyfillPlugin() {
	return {
		name: 'dynamic-import-polyfill',
		renderDynamicImport() {
			return {
				left: 'dynamicImportPolyfill(',
				right: ', import.meta.url)'
			};
		}
	};
}

// input
import('./lib.js');

// output
dynamicImportPolyfill('./lib.js', import.meta.url);
function dynamicImportPolyfillPlugin() {
	return {
		name: 'dynamic-import-polyfill',
		renderDynamicImport() {
			return {
				left: 'dynamicImportPolyfill(',
				right: ', import.meta.url)'
			};
		}
	};
}

// input
import('./lib.js');

// output
dynamicImportPolyfill('./lib.js', import.meta.url);

下一个插件将确保 esm-lib 的所有动态导入都标记为外部并保留为导入表达式,例如 允许 CommonJS 构建在 Node 13+ 中导入 ES 模块,参见。 如何在 Node 文档中进行 从 CommonJS 导入 ES 模块

The next plugin will make sure all dynamic imports of esm-lib are marked as external and retained as import expressions to e.g. allow a CommonJS build to import an ES module in Node 13+, cf. how to import ES modules from CommonJS in the Node documentation.

js
function retainImportExpressionPlugin() {
	return {
		name: 'retain-import-expression',
		resolveDynamicImport(specifier) {
			if (specifier === 'esm-lib') return false;
			return null;
		},
		renderDynamicImport({ targetModuleId }) {
			if (targetModuleId === 'esm-lib') {
				return {
					left: 'import(',
					right: ')'
				};
			}
		}
	};
}
function retainImportExpressionPlugin() {
	return {
		name: 'retain-import-expression',
		resolveDynamicImport(specifier) {
			if (specifier === 'esm-lib') return false;
			return null;
		},
		renderDynamicImport({ targetModuleId }) {
			if (targetModuleId === 'esm-lib') {
				return {
					left: 'import(',
					right: ')'
				};
			}
		}
	};
}

请注意,当此钩子以非 ES 格式重写动态导入时,没有互操作代码来确保例如 生成 .default 后,默认导出可用。 插件的责任是确保重写的动态导入返回一个解析为正确的命名空间对象的 Promise。

Note that when this hook rewrites dynamic imports in non-ES formats, no interop code to make sure that e.g. the default export is available as .default is generated. It is the responsibility of the plugin to make sure the rewritten dynamic import returns a Promise that resolves to a proper namespace object.

renderError

类型:(error: Error) => void
种类:异步、并行
以前的:renderStartrenderChunk 的任何钩子
下一个:如果调用它,则这是输出生成阶段的最后一个钩子,如果生成另一个输出,则可能会再次跟随 outputOptions

当 Rollup 在 bundle.generate()bundle.write() 期间遇到错误时调用。 错误被传递给这个钩子。 要在生成成功完成时收到通知,请使用 generateBundle 钩子。

Called when Rollup encounters an error during bundle.generate() or bundle.write(). The error is passed to this hook. To get notified when generation completes successfully, use the generateBundle hook.

renderStart

类型:(outputOptions: OutputOptions, inputOptions: InputOptions) => void
种类:异步、并行
以前的:outputOptions
下一个:renderDynamicImport 对于第一个块中的每个动态导入表达式

每次调用 bundle.generate()bundle.write() 时都会首先调用。 要在生成完成时收到通知,请使用 generateBundlerenderError 钩子。 当你需要访问传递给 bundle.generate()bundle.write() 的输出选项时,建议使用此钩子,因为它考虑了所有 outputOptions 钩子的转换,并且还包含未设置选项的正确默认值。 它还接收传递给 rollup.rollup() 的输入选项,以便可用作输出插件的插件(即仅使用 generate 阶段钩子的插件)可以访问它们。

Called initially each time bundle.generate() or bundle.write() is called. To get notified when generation has completed, use the generateBundle and renderError hooks. This is the recommended hook to use when you need access to the output options passed to bundle.generate() or bundle.write() as it takes the transformations by all outputOptions hooks into account and also contains the right default values for unset options. It also receives the input options passed to rollup.rollup() so that plugins that can be used as output plugins, i.e. plugins that only use generate phase hooks, can get access to them.

resolveFileUrl

类型:ResolveFileUrlHook
种类:同步,首先
以前的:renderDynamicImport 对于当前块中的每个动态导入表达式
下一个:当前 chunk 并行 banner, footer, intro, outro
typescript
type ResolveFileUrlHook = (options: {
	chunkId: string;
	fileName: string;
	format: InternalModuleFormat;
	moduleId: string;
	referenceId: string;
	relativePath: string;
}) => string | NullValue;
type ResolveFileUrlHook = (options: {
	chunkId: string;
	fileName: string;
	format: InternalModuleFormat;
	moduleId: string;
	referenceId: string;
	relativePath: string;
}) => string | NullValue;

允许自定义 Rollup 如何解析插件通过 this.emitFile 触发的文件的 URL。 默认情况下,Rollup 将为 import.meta.ROLLUP_FILE_URL_referenceId 生成代码,该代码应正确生成触发文件的绝对 URL,与输出格式和部署代码的主机系统无关。

Allows to customize how Rollup resolves URLs of files that were emitted by plugins via this.emitFile. By default, Rollup will generate code for import.meta.ROLLUP_FILE_URL_referenceId that should correctly generate absolute URLs of emitted files independent of the output format and the host system where the code is deployed.

为此,除 CommonJS 和 UMD 之外的所有格式都假定它们在 URLdocument 可用的浏览器环境中运行。 如果失败或生成更优化的代码,可以使用此钩子来自定义此行为。 为此,可以使用以下信息:

For that, all formats except CommonJS and UMD assume that they run in a browser environment where URL and document are available. In case that fails or to generate more optimized code, this hook can be used to customize this behaviour. To do that, the following information is available:

  • chunkId: 引用此文件的块的 ID。 如果块文件名包含哈希值,则此 id 将包含占位符。 如果此占位符最终出现在生成的代码中,则 Rollup 会将该占位符替换为实际文件名。
  • fileName: 触发的文件的路径和文件名,相对于 output.dir,不带前导 ./。 同样,如果这是一个名称中包含哈希值的块,那么它将包含一个占位符。
  • format: 渲染的输出格式。
  • moduleId: 引用此文件的原始模块的 ID。 对于有条件地以不同方式解决某些资源很有用。
  • referenceId: 文件的参考 ID。
  • relativePath: 触发的文件的路径和文件名,相对于引用文件的块。 该路径将不包含前导 ./,但可能包含前导 ../

以下插件将始终解析与当前文档相关的所有文件:

The following plugin will always resolve all files relative to the current document:

js
function resolveToDocumentPlugin() {
	return {
		name: 'resolve-to-document',
		resolveFileUrl({ fileName }) {
			return `new URL('${fileName}', document.baseURI).href`;
		}
	};
}
function resolveToDocumentPlugin() {
	return {
		name: 'resolve-to-document',
		resolveFileUrl({ fileName }) {
			return `new URL('${fileName}', document.baseURI).href`;
		}
	};
}

resolveImportMeta

类型:(property: string | null, {chunkId: string, moduleId: string, format: string}) => string | null
种类:同步,首先
以前的:renderDynamicImport 对于当前块中的每个动态导入表达式
下一个:当前 chunk 并行 banner, footer, intro, outro

允许自定义 Rollup 如何处理 import.metaimport.meta.someProperty,特别是 import.meta.url。 在 ES 模块中,import.meta 是一个对象,import.meta.url 包含当前模块的 URL,例如 浏览器中为 http://server.net/bundle.js,Node 中为 file:///path/to/bundle.js

Allows to customize how Rollup handles import.meta and import.meta.someProperty, in particular import.meta.url. In ES modules, import.meta is an object and import.meta.url contains the URL of the current module, e.g. http://server.net/bundle.js for browsers or file:///path/to/bundle.js in Node.

默认情况下,对于 ES 模块以外的格式,Rollup 会将 import.meta.url 替换为尝试通过返回当前块的动态 URL 来匹配此行为的代码。 请注意,除 CommonJS 和 UMD 之外的所有格式都假定它们在 URLdocument 可用的浏览器环境中运行。 对于其他属性,import.meta.someProperty 将替换为 undefined,而 import.meta 将替换为包含 url 属性的对象。

By default, for formats other than ES modules, Rollup replaces import.meta.url with code that attempts to match this behaviour by returning the dynamic URL of the current chunk. Note that all formats except CommonJS and UMD assume that they run in a browser environment where URL and document are available. For other properties, import.meta.someProperty is replaced with undefined while import.meta is replaced with an object containing a url property.

这种行为是可以改变的—也适用于 ES 模块—通过这个钩子。 对于每次出现 import.meta<.someProperty>,都会使用属性名称或 null(如果直接访问 import.meta)调用此钩子。 例如,以下代码将使用原始模块到当前工作目录的相对路径解析 import.meta.url,并在运行时再次根据当前文档的基本 URL 解析此路径:

This behaviour can be changed—also for ES modules—via this hook. For each occurrence of import.meta<.someProperty>, this hook is called with the name of the property or null if import.meta is accessed directly. For example, the following code will resolve import.meta.url using the relative path of the original module to the current working directory and again resolve this path against the base URL of the current document at runtime:

js
function importMetaUrlCurrentModulePlugin() {
	return {
		name: 'import-meta-url-current-module',
		resolveImportMeta(property, { moduleId }) {
			if (property === 'url') {
				return `new URL('${path.relative(
					process.cwd(),
					moduleId
				)}', document.baseURI).href`;
			}
			return null;
		}
	};
}
function importMetaUrlCurrentModulePlugin() {
	return {
		name: 'import-meta-url-current-module',
		resolveImportMeta(property, { moduleId }) {
			if (property === 'url') {
				return `new URL('${path.relative(
					process.cwd(),
					moduleId
				)}', document.baseURI).href`;
			}
			return null;
		}
	};
}

如果 chunkId 包含哈希值,它将包含一个占位符。 如果此占位符最终出现在生成的代码中,Rollup 会将其替换为实际的块哈希。

If the chunkId would contain a hash, it will contain a placeholder instead. If this placeholder ends up in the generated code, Rollup will replace it with the actual chunk hash.

writeBundle

类型:(options: OutputOptions, bundle: { [fileName: string]: AssetInfo | ChunkInfo }) => void
种类:异步、并行
以前的:generateBundle
下一个:如果调用它,则这是输出生成阶段的最后一个钩子,如果生成另一个输出,则可能会再次跟随 outputOptions

仅在写入所有文件后在 bundle.write() 末尾调用。 与 generateBundle 钩子类似,bundle 提供正在写入的文件的完整列表及其详细信息。

Called only at the end of bundle.write() once all files have been written. Similar to the generateBundle hook, bundle provides the full list of files being written along with their details.

插件上下文

可以通过 this 从大多数 钩子 内访问许多实用函数和信息位:

A number of utility functions and informational bits can be accessed from within most hooks via this:

this.addWatchFile

类型:(id: string) => void

添加要在监视模式下监视的其他文件,以便对这些文件的更改将触发重建。 id 可以是文件或目录的绝对路径,也可以是相对于当前工作目录的路径。 该上下文函数可以在除 closeBundle 之外的所有插件钩子中使用。 但如果将 watch.skipWrite 设置为 true,则在 输出生成钩子 中使用时不会产生任何效果。

Adds additional files to be monitored in watch mode so that changes to these files will trigger rebuilds. id can be an absolute path to a file or directory or a path relative to the current working directory. This context function can be used in all plugin hooks except closeBundle. However, it will not have an effect when used in output generation hooks if watch.skipWrite is set to true.

注意: 通常在监视模式下为了提高重建速度,只有当给定模块的内容实际发生更改时,才会触发 transform 钩子。 在 transform 钩子中使用 this.addWatchFile 将确保如果监视的文件发生更改,也会重新评估该模块的 transform 钩子。

Note: Usually in watch mode to improve rebuild speed, the transform hook will only be triggered for a given module if its contents actually changed. Using this.addWatchFile from within the transform hook will make sure the transform hook is also reevaluated for this module if the watched file changes.

一般来说,建议在取决于监视文件的钩子内使用 this.addWatchFile

In general, it is recommended to use this.addWatchFile from within the hook that depends on the watched file.

this.debug

类型:(log: string | RollupLog | (() => RollupLog | string), position?: number | { column: number; line: number }) => void

生成 "debug" 日志。 详情请参见 this.warn。 调试日志总是由 Rollup 添加 code: "PLUGIN_LOG"。 确保向这些日志添加独特的 pluginCode 以便于过滤。

Generate a "debug" log. See this.warn for details. Debug logs always get code: "PLUGIN_LOG" added by Rollup. Make sure to add a distinctive pluginCode to those logs for easy filtering.

仅当 logLevel 选项显式设置为 "debug" 时才会处理这些日志,否则不执行任何操作。 因此,鼓励向插件添加有用的调试日志,因为这可以帮助发现问题,同时默认情况下它们将被有效地静音。 如果你需要进行昂贵的计算来生成日志,请确保使用函数形式,以便仅在实际处理日志时才执行这些计算。

These logs are only processed if the logLevel option is explicitly set to "debug", otherwise it does nothing. Therefore, it is encouraged to add helpful debug logs to plugins as that can help spot issues while they will be efficiently muted by default. If you need to do expensive computations to generate the log, make sure to use the function form so that these computations are only performed if the log is actually processed.

js
function plugin() {
	return {
		name: 'test',
		transform(code, id) {
			this.debug(
				() =>
					`transforming ${id},\n` +
					`module contains, ${code.split('\n').length} lines`
			);
		}
	};
}
function plugin() {
	return {
		name: 'test',
		transform(code, id) {
			this.debug(
				() =>
					`transforming ${id},\n` +
					`module contains, ${code.split('\n').length} lines`
			);
		}
	};
}

this.emitFile

类型:(emittedFile: EmittedChunk | EmittedPrebuiltChunk | EmittedAsset) => string
typescript
interface EmittedChunk {
	type: 'chunk';
	id: string;
	name?: string;
	fileName?: string;
	implicitlyLoadedAfterOneOf?: string[];
	importer?: string;
	preserveSignature?: 'strict' | 'allow-extension' | 'exports-only' | false;
}

interface EmittedPrebuiltChunk {
	type: 'prebuilt-chunk';
	fileName: string;
	code: string;
	exports?: string[];
	map?: SourceMap;
}

interface EmittedAsset {
	type: 'asset';
	name?: string;
	needsCodeReference?: boolean;
	fileName?: string;
	source?: string | Uint8Array;
}
interface EmittedChunk {
	type: 'chunk';
	id: string;
	name?: string;
	fileName?: string;
	implicitlyLoadedAfterOneOf?: string[];
	importer?: string;
	preserveSignature?: 'strict' | 'allow-extension' | 'exports-only' | false;
}

interface EmittedPrebuiltChunk {
	type: 'prebuilt-chunk';
	fileName: string;
	code: string;
	exports?: string[];
	map?: SourceMap;
}

interface EmittedAsset {
	type: 'asset';
	name?: string;
	needsCodeReference?: boolean;
	fileName?: string;
	source?: string | Uint8Array;
}

触发一个包含在构建输出中的新文件,并返回一个 referenceId,可在各个地方使用该文件来引用触发的文件。 你可以触发块、预构建块或资源。

Emits a new file that is included in the build output and returns a referenceId that can be used in various places to reference the emitted file. You can emit chunks, prebuilt chunks or assets.

当触发块或资源时,可以提供 namefileName。 如果提供了 fileName,它将不加修改地用作生成文件的名称,如果这导致冲突,则会抛出错误。 否则,如果提供了 name,它将用作相应 output.chunkFileNamesoutput.assetFileNames 模式中 [name] 的替换,可能会在文件名末尾添加一个唯一的数字以避免冲突。 如果未提供 namefileName,则将使用默认名称。 预构建块必须始终具有 fileName

When emitting chunks or assets, either a name or a fileName can be supplied. If a fileName is provided, it will be used unmodified as the name of the generated file, throwing an error if this causes a conflict. Otherwise, if a name is supplied, this will be used as substitution for [name] in the corresponding output.chunkFileNames or output.assetFileNames pattern, possibly adding a unique number to the end of the file name to avoid conflicts. If neither a name nor fileName is supplied, a default name will be used. Prebuilt chunks must always have a fileName.

你可以在 loadtransform 插件钩子通过 import.meta.ROLLUP_FILE_URL_referenceId 返回的任何代码中引用触发文件的 URL。 有关更多详细信息和示例,请参阅 文件网址

You can reference the URL of an emitted file in any code returned by a load or transform plugin hook via import.meta.ROLLUP_FILE_URL_referenceId. See File URLs for more details and an example.

可以通过 resolveFileUrl 插件钩子自定义替换 import.meta.ROLLUP_FILE_URL_referenceId 的生成代码。 你还可以在文件名可用时立即使用 this.getFileName(referenceId) 来确定该文件名。 如果没有显式设置文件名,则

The generated code that replaces import.meta.ROLLUP_FILE_URL_referenceId can be customized via the resolveFileUrl plugin hook. You can also use this.getFileName(referenceId) to determine the file name as soon as it is available. If the file name is not set explicitly, then

  • 资源文件名以 renderStart 钩子开头。 对于稍后触发的资源,文件名将在触发资源后立即可用。
  • renderStart 钩子之后创建块后,不包含哈希的块文件名就可用。
  • 如果块文件名包含哈希值,则在 generateBundle 之前的任何钩子中使用 getFileName 将返回包含占位符的名称,而不是实际名称。 如果你在 renderChunk 中转换的块中使用此文件名或其部分内容,则 Rollup 会将占位符替换为 generateBundle 之前的实际哈希值,确保哈希值反映最终生成的块的实际内容,包括所有引用的文件哈希值。

如果 typechunk,那么这会触发一个以给定模块 id 作为入口点的新块。 为了解决这个问题,id 将像常规入口点一样通过构建钩子,从 resolveId 开始。 如果提供了 importer,则它充当 resolveId 的第二个参数,对于正确解析相对路径非常重要。 如果未提供,路径将相对于当前工作目录进行解析。 如果提供了 preserveSignature 的值,这将覆盖该特定块的 preserveEntrySignatures

If the type is chunk, then this emits a new chunk with the given module id as entry point. To resolve it, the id will be passed through build hooks just like regular entry points, starting with resolveId. If an importer is provided, this acts as the second parameter of resolveId and is important to properly resolve relative paths. If it is not provided, paths will be resolved relative to the current working directory. If a value for preserveSignature is provided, this will override preserveEntrySignatures for this particular chunk.

这不会导致图中出现重复的模块,相反,如果有必要,现有的块将被分割或创建具有重新导出的外观块。 具有指定 fileName 的块将始终生成单独的块,而其他触发的块可能会与现有块进行数据去重,即使 name 不匹配也是如此。 如果此类块未进行数据去重,则将使用 output.chunkFileNames 名称模式。

This will not result in duplicate modules in the graph, instead if necessary, existing chunks will be split or a facade chunk with reexports will be created. Chunks with a specified fileName will always generate separate chunks while other emitted chunks may be deduplicated with existing chunks even if the name does not match. If such a chunk is not deduplicated, the output.chunkFileNames name pattern will be used.

默认情况下,Rollup 假定触发的块是独立于其他入口点执行的,甚至可能在执行任何其他代码之前执行。 这意味着,如果触发的块与现有入口点共享依赖,Rollup 将为这些入口点之间共享的依赖创建一个附加块。 为 implicitlyLoadedAfterOneOf 提供非空模块 id 数组将改变该行为,方法是为 Rollup 提供附加信息以防止在某些情况下发生这种情况。 这些 ID 将以与 id 属性相同的方式解析,并尊重 importer 属性(如果提供)。 Rollup 现在将假设仅当导致 implicitlyLoadedAfterOneOf 中的 id 之一被加载的至少一个入口点已被执行时才执行触发的块,创建相同的块,就好像新触发的块只能通过动态访问一样 从 implicitlyLoadedAfterOneOf 中的模块导入。 下面是一个示例,它使用它创建一个包含多个脚本的简单 HTML 文件,创建优化的块以尊重它们的执行顺序:

By default, Rollup assumes that emitted chunks are executed independent of other entry points, possibly even before any other code is executed. This means that if an emitted chunk shares a dependency with an existing entry point, Rollup will create an additional chunk for dependencies that are shared between those entry points. Providing a non-empty array of module ids for implicitlyLoadedAfterOneOf will change that behaviour by giving Rollup additional information to prevent this in some cases. Those ids will be resolved the same way as the id property, respecting the importer property if it is provided. Rollup will now assume that the emitted chunk is only executed if at least one of the entry points that lead to one of the ids in implicitlyLoadedAfterOneOf being loaded has already been executed, creating the same chunks as if the newly emitted chunk was only reachable via dynamic import from the modules in implicitlyLoadedAfterOneOf. Here is an example that uses this to create a simple HTML file with several scripts, creating optimized chunks to respect their execution order:

js
// rollup.config.js
function generateHtmlPlugin() {
	let ref1, ref2, ref3;
	return {
		name: 'generate-html',
		buildStart() {
			ref1 = this.emitFile({
				type: 'chunk',
				id: 'src/entry1'
			});
			ref2 = this.emitFile({
				type: 'chunk',
				id: 'src/entry2',
				implicitlyLoadedAfterOneOf: ['src/entry1']
			});
			ref3 = this.emitFile({
				type: 'chunk',
				id: 'src/entry3',
				implicitlyLoadedAfterOneOf: ['src/entry2']
			});
		},
		generateBundle() {
			this.emitFile({
				type: 'asset',
				fileName: 'index.html',
				source: `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="UTF-8">
          <title>Title</title>
         </head>
        <body>
          <script src="${this.getFileName(ref1)}" type="module"></script>
          <script src="${this.getFileName(ref2)}" type="module"></script>
          <script src="${this.getFileName(ref3)}" type="module"></script>
        </body>
        </html>`
			});
		}
	};
}

export default {
	input: [],
	preserveEntrySignatures: false,
	plugins: [generateHtmlPlugin()],
	output: {
		format: 'es',
		dir: 'dist'
	}
};
// rollup.config.js
function generateHtmlPlugin() {
	let ref1, ref2, ref3;
	return {
		name: 'generate-html',
		buildStart() {
			ref1 = this.emitFile({
				type: 'chunk',
				id: 'src/entry1'
			});
			ref2 = this.emitFile({
				type: 'chunk',
				id: 'src/entry2',
				implicitlyLoadedAfterOneOf: ['src/entry1']
			});
			ref3 = this.emitFile({
				type: 'chunk',
				id: 'src/entry3',
				implicitlyLoadedAfterOneOf: ['src/entry2']
			});
		},
		generateBundle() {
			this.emitFile({
				type: 'asset',
				fileName: 'index.html',
				source: `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="UTF-8">
          <title>Title</title>
         </head>
        <body>
          <script src="${this.getFileName(ref1)}" type="module"></script>
          <script src="${this.getFileName(ref2)}" type="module"></script>
          <script src="${this.getFileName(ref3)}" type="module"></script>
        </body>
        </html>`
			});
		}
	};
}

export default {
	input: [],
	preserveEntrySignatures: false,
	plugins: [generateHtmlPlugin()],
	output: {
		format: 'es',
		dir: 'dist'
	}
};

如果没有动态导入,这将创建三个块,其中第一个块包含 src/entry1 的所有依赖,第二个块仅包含第一个块中未包含的 src/entry2 的依赖,从第一个块导入这些依赖,然后再次导入 第三块也一样。

If there are no dynamic imports, this will create exactly three chunks where the first chunk contains all dependencies of src/entry1, the second chunk contains only the dependencies of src/entry2 that are not contained in the first chunk, importing those from the first chunk, and again the same for the third chunk.

请注意,即使任何模块 id 都可以在 implicitlyLoadedAfterOneOf 中使用,如果此类 id 无法与块唯一关联,则 Rollup 将抛出错误,例如 因为 id 无法从现有的静态入口点隐式或显式到达,或者因为该文件完全是树状摇动的。 不过,仅使用由用户定义的或先前触发的块的入口点总是有效的。

Note that even though any module id can be used in implicitlyLoadedAfterOneOf, Rollup will throw an error if such an id cannot be uniquely associated with a chunk, e.g. because the id cannot be reached implicitly or explicitly from the existing static entry points, or because the file is completely tree-shaken. Using only entry points, either defined by the user or of previously emitted chunks, will always work, though.

如果 typeprebuilt-chunk,它会触发一个具有 code 参数提供的固定内容的块。 目前,fileName 还需要提供块的名称。 如果它导出一些变量,我们应该通过可选的 exports 列出这些变量。 通过 map,我们可以提供对应于 code 的源映射。

If the type is prebuilt-chunk, it emits a chunk with fixed contents provided by the code parameter. At the moment, fileName is also required to provide the name of the chunk. If it exports some variables, we should list these via the optional exports. Via map we can provide a sourcemap that corresponds to code.

要在导入中引用预构建块,我们需要在 resolveId 钩子中将 "模块" 标记为外部,因为预构建块不是模块图的一部分。 相反,它们的行为就像带有块元数据的资源:

To reference a prebuilt chunk in imports, we need to mark the "module" as external in the resolveId hook as prebuilt chunks are not part of the module graph. Instead, they behave like assets with chunk meta-data:

js
function emitPrebuiltChunkPlugin() {
	return {
		name: 'emit-prebuilt-chunk',
		resolveId(source) {
			if (source === './my-prebuilt-chunk.js') {
				return {
					id: source,
					external: true
				};
			}
		},
		buildStart() {
			this.emitFile({
				type: 'prebuilt-chunk',
				fileName: 'my-prebuilt-chunk.js',
				code: 'export const foo = "foo"',
				exports: ['foo']
			});
		}
	};
}
function emitPrebuiltChunkPlugin() {
	return {
		name: 'emit-prebuilt-chunk',
		resolveId(source) {
			if (source === './my-prebuilt-chunk.js') {
				return {
					id: source,
					external: true
				};
			}
		},
		buildStart() {
			this.emitFile({
				type: 'prebuilt-chunk',
				fileName: 'my-prebuilt-chunk.js',
				code: 'export const foo = "foo"',
				exports: ['foo']
			});
		}
	};
}

然后你可以在代码中引用预构建的块:

Then you can reference the prebuilt chunk in your code:

js
import { foo } from './my-prebuilt-chunk.js';
import { foo } from './my-prebuilt-chunk.js';

目前,触发预构建块是一项基本功能。 期待你的反馈。

Currently, emitting a prebuilt chunk is a basic feature. Looking forward to your feedback.

如果 typeasset,则这会触发一个以给定 source 作为内容的任意新文件。 可以将 source 通过 this.setAssetSource(referenceId, source) 的设置推迟到稍后的时间,以便能够在构建阶段引用文件,同时在生成阶段为每个输出单独设置源。 具有指定 fileName 的资源将始终生成单独的文件,而其他触发的资源如果具有相同的源,则可能会与现有资源进行数据去重,即使 name 不匹配也是如此。 如果没有对没有 fileName 的资源进行数据去重,则将使用 output.assetFileNames 名称模式。 如果 needsCodeReference 设置为 true,并且该资源未通过 import.meta.ROLLUP_FILE_URL_referenceId 输出中的任何代码引用,则 Rollup 将不会触发它。 这也尊重通过树摇动删除的引用,即,如果相应的 import.meta.ROLLUP_FILE_URL_referenceId 是源代码的一部分但实际上并未使用,并且引用通过树摇动删除,则不会触发资源。

If the type is asset, then this emits an arbitrary new file with the given source as content. It is possible to defer setting the source via this.setAssetSource(referenceId, source) to a later time to be able to reference a file during the build phase while setting the source separately for each output during the generate phase. Assets with a specified fileName will always generate separate files while other emitted assets may be deduplicated with existing assets if they have the same source even if the name does not match. If an asset without a fileName is not deduplicated, the output.assetFileNames name pattern will be used. If needsCodeReference is set to true and this asset is not referenced by any code in the output via import.meta.ROLLUP_FILE_URL_referenceId, then Rollup will not emit it. This also respects references removed via tree-shaking, i.e. if the corresponding import.meta.ROLLUP_FILE_URL_referenceId is part of the source code but is not actually used and the reference is removed by tree-shaking, then the asset is not emitted.

this.error

类型:(error: string | RollupLog | Error, position?: number | { column: number; line: number }) => never

结构上与 this.warn 相同,但它也会因错误而中止打包过程。 有关 RollupLog 类型的信息,请参阅 onLog 选项。

Structurally equivalent to this.warn, except that it will also abort the bundling process with an error. See the onLog option for information about the RollupLog type.

如果传递了 Error 实例,它将按原样使用,否则将使用给定的错误消息和所有其他提供的属性创建一个新的 Error 实例。

If an Error instance is passed, it will be used as-is, otherwise a new Error instance will be created with the given error message and all additional provided properties.

在除 onLog 钩子之外的所有钩子中,错误都会因 code: "PLUGIN_ERROR"plugin: plugin.name 属性而增加。 如果 acode 属性已经存在并且代码不是以 PLUGIN_ 开头,它将被重命名为 pluginCode

In all hooks except the onLog hook, the error will be augmented with code: "PLUGIN_ERROR" and plugin: plugin.nameproperties. If acodeproperty already exists and the code does not start withPLUGIN_, it will be renamed to pluginCode.

onLog 钩子中,此函数是一种将警告转变为错误的简单方法,同时保留警告的所有附加属性:

In the onLog hook, this function is an easy way to turn warnings into errors while keeping all additional properties of the warning:

js
function myPlugin() {
	return {
		name: 'my-plugin',
		onLog(level, log) {
			if (level === 'warn' && log.code === 'THIS_IS_NOT_OK') {
				return this.error(log);
			}
		}
	};
}
function myPlugin() {
	return {
		name: 'my-plugin',
		onLog(level, log) {
			if (level === 'warn' && log.code === 'THIS_IS_NOT_OK') {
				return this.error(log);
			}
		}
	};
}

当在 transform 钩子中使用时,当前模块的 id 也会被添加,并且可以提供 position。 这是一个字符索引或文件位置,将用于使用 posloc(标准 { file, line, column } 对象)和 frame(显示位置的代码片段)来扩充日志。

When used in the transform hook, the id of the current module will also be added and a position can be supplied. This is a character index or file location which will be used to augment the log with pos, loc (a standard { file, line, column } object) and frame (a snippet of code showing the location).

this.getCombinedSourcemap

类型:() => SourceMap

获取所有以前插件的组合源映射。 此上下文函数只能在 transform 插件钩子中使用。

Get the combined source maps of all previous plugins. This context function can only be used in the transform plugin hook.

this.getFileName

类型:(referenceId: string) => string

获取已通过 this.emitFile 触发的块或资源的文件名。 文件名将相对于 outputOptions.dir

Get the file name of a chunk or asset that has been emitted via this.emitFile. The file name will be relative to outputOptions.dir.

this.getModuleIds

类型:() => IterableIterator<string>

返回一个 Iterator,它允许访问当前图中的所有模块 id。 可以通过迭代

Returns an Iterator that gives access to all module ids in the current graph. It can be iterated via

js
for (const moduleId of this.getModuleIds()) {
	/* ... */
}
for (const moduleId of this.getModuleIds()) {
	/* ... */
}

或通过 Array.from(this.getModuleIds()) 转换为数组。

or converted into an Array via Array.from(this.getModuleIds()).

this.getModuleInfo

类型:(moduleId: string) => (ModuleInfo | null)
typescript
interface ModuleInfo {
	id: string; // the id of the module, for convenience
	code: string | null; // the source code of the module, `null` if external or not yet available
	ast: ESTree.Program; // the parsed abstract syntax tree if available
	hasDefaultExport: boolean | null; // is there a default export, `null` if external or not yet available
	isEntry: boolean; // is this a user- or plugin-defined entry point
	isExternal: boolean; // for external modules that are referenced but not included in the graph
	isIncluded: boolean | null; // is the module included after tree-shaking, `null` if external or not yet available
	importedIds: string[]; // the module ids statically imported by this module
	importedIdResolutions: ResolvedId[]; // how statically imported ids were resolved, for use with this.load
	importers: string[]; // the ids of all modules that statically import this module
	exportedBindings: Record<string, string[]> | null; // contains all exported variables associated with the path of `from`, `null` if external
	exports: string[] | null; // all exported variables, `null` if external
	dynamicallyImportedIds: string[]; // the module ids imported by this module via dynamic import()
	dynamicallyImportedIdResolutions: ResolvedId[]; // how ids imported via dynamic import() were resolved
	dynamicImporters: string[]; // the ids of all modules that import this module via dynamic import()
	implicitlyLoadedAfterOneOf: string[]; // implicit relationships, declared via this.emitFile
	implicitlyLoadedBefore: string[]; // implicit relationships, declared via this.emitFile
	attributes: { [key: string]: string }; // import attributes for this module
	meta: { [plugin: string]: any }; // custom module meta-data
	moduleSideEffects: boolean | 'no-treeshake'; // are imports of this module included if nothing is imported from it
	syntheticNamedExports: boolean | string; // final value of synthetic named exports
}

interface ResolvedId {
	id: string; // the id of the imported module
	external: boolean | 'absolute'; // is this module external, "absolute" means it will not be rendered as relative in the module
	attributes: { [key: string]: string }; // import attributes for this import
	meta: { [plugin: string]: any }; // custom module meta-data when resolving the module
	moduleSideEffects: boolean | 'no-treeshake'; // are side effects of the module observed, is tree-shaking enabled
	resolvedBy: string; // which plugin resolved this module, "rollup" if resolved by Rollup itself
	syntheticNamedExports: boolean | string; // does the module allow importing non-existing named exports
}
interface ModuleInfo {
	id: string; // the id of the module, for convenience
	code: string | null; // the source code of the module, `null` if external or not yet available
	ast: ESTree.Program; // the parsed abstract syntax tree if available
	hasDefaultExport: boolean | null; // is there a default export, `null` if external or not yet available
	isEntry: boolean; // is this a user- or plugin-defined entry point
	isExternal: boolean; // for external modules that are referenced but not included in the graph
	isIncluded: boolean | null; // is the module included after tree-shaking, `null` if external or not yet available
	importedIds: string[]; // the module ids statically imported by this module
	importedIdResolutions: ResolvedId[]; // how statically imported ids were resolved, for use with this.load
	importers: string[]; // the ids of all modules that statically import this module
	exportedBindings: Record<string, string[]> | null; // contains all exported variables associated with the path of `from`, `null` if external
	exports: string[] | null; // all exported variables, `null` if external
	dynamicallyImportedIds: string[]; // the module ids imported by this module via dynamic import()
	dynamicallyImportedIdResolutions: ResolvedId[]; // how ids imported via dynamic import() were resolved
	dynamicImporters: string[]; // the ids of all modules that import this module via dynamic import()
	implicitlyLoadedAfterOneOf: string[]; // implicit relationships, declared via this.emitFile
	implicitlyLoadedBefore: string[]; // implicit relationships, declared via this.emitFile
	attributes: { [key: string]: string }; // import attributes for this module
	meta: { [plugin: string]: any }; // custom module meta-data
	moduleSideEffects: boolean | 'no-treeshake'; // are imports of this module included if nothing is imported from it
	syntheticNamedExports: boolean | string; // final value of synthetic named exports
}

interface ResolvedId {
	id: string; // the id of the imported module
	external: boolean | 'absolute'; // is this module external, "absolute" means it will not be rendered as relative in the module
	attributes: { [key: string]: string }; // import attributes for this import
	meta: { [plugin: string]: any }; // custom module meta-data when resolving the module
	moduleSideEffects: boolean | 'no-treeshake'; // are side effects of the module observed, is tree-shaking enabled
	resolvedBy: string; // which plugin resolved this module, "rollup" if resolved by Rollup itself
	syntheticNamedExports: boolean | string; // does the module allow importing non-existing named exports
}

返回有关相关模块的附加信息。

Returns additional information about the module in question.

在构建过程中,该对象表示有关模块的当前可用信息,这些信息在 buildEnd 钩子之前可能不准确:

During the build, this object represents currently available information about the module which may be inaccurate before the buildEnd hook:

  • idisExternal 永远不会改变。
  • codeasthasDefaultExportexportsexportedBindings 仅在解析后可用,即在 moduleParsed 钩子中或等待 this.load 后。 到那时,他们将不再改变。
  • 如果 isEntrytrue,则不再改变。 然而,模块在解析后有可能成为入口点,无论是通过 this.emitFile 还是因为插件在解析入口点时通过 resolveId 钩子中的 this.load 检查潜在的入口点。 因此,不建议在 transform 钩子中依赖此标志。 buildEnd 年后不再改变。
  • 同样,implicitlyLoadedAfterOneOf 可以在 buildEnd 之前的任何时间通过 this.emitFile 接收附加条目。
  • importersdynamicImportersimplicitlyLoadedBefore 将以空数组开始,当发现新的导入器和隐式依赖时,它们会接收附加条目。 buildEnd 之后它们将不再改变。
  • isIncluded 仅在 buildEnd 之后可用,此时它将不再改变。
  • 当模块已被解析并且其依赖已被解析时,importedIdsimportedIdResolutionsdynamicallyImportedIdsdynamicallyImportedIdResolutions 可用。 这是在 moduleParsed 钩子中或在使用 resolveDependencies 标志等待 this.load 之后的情况。 到那时,他们将不再改变。
  • attributesmetamoduleSideEffectssyntheticNamedExports 可以通过 loadtransform 钩子更改。 此外,虽然大多数属性是只读的,但这些属性是可写的,并且如果在触发 buildEnd 钩子之前发生更改,则会拾取更改。 meta 本身不应被覆盖,但可以随时改变其属性以存储有关模块的元信息。 这样做而不是在插件中保留状态的优点是,如果使用了 meta,它会被持久化到缓存并从缓存中恢复,例如 从 CLI 使用监视模式时。

如果找不到模块 ID,则返回 null

Returns null if the module id cannot be found.

this.getWatchFiles

类型:() => string[]

获取之前监视过的文件的 ID。 包括由带有 this.addWatchFile 的插件添加的文件以及在构建期间由 Rollup 隐式添加的文件。

Get ids of the files which has been watched previously. Include both files added by plugins with this.addWatchFile and files added implicitly by Rollup during the build.

this.info

类型:(log: string | RollupLog | (() => RollupLog | string), position?: number | { column: number; line: number }) => void

生成 "info" 日志。 详情请参见 this.warn。 信息日志总是由 Rollup 添加 code: "PLUGIN_LOG"。 由于这些日志是默认显示的,因此将它们用于非警告但有意义的信息,以在每个构建中向所有用户显示。

Generate an "info" log. See this.warn for details. Info logs always get code: "PLUGIN_LOG" added by Rollup. As these logs are displayed by default, use them for information that is not a warning but makes sense to display to all users on every build.

如果 logLevel 选项设置为 "warn""silent",则此方法将不执行任何操作。

If the logLevel option is set to "warn" or "silent", this method will do nothing.

this.load

类型:Load
typescript
type Load = (options: {
	id: string;
	resolveDependencies?: boolean;
	attributes?: Record<string, string> | null;
	meta?: CustomPluginOptions | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	syntheticNamedExports?: boolean | string | null;
}) => Promise<ModuleInfo>;
type Load = (options: {
	id: string;
	resolveDependencies?: boolean;
	attributes?: Record<string, string> | null;
	meta?: CustomPluginOptions | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	syntheticNamedExports?: boolean | string | null;
}) => Promise<ModuleInfo>;

加载并解析与给定 id 对应的模块,将附加元信息附加到模块(如果提供)。 这将触发相同的 loadtransformmoduleParsed 钩子,如果该模块被另一个模块导入,这些钩子也会被触发。

Loads and parses the module corresponding to the given id, attaching additional meta information to the module if provided. This will trigger the same load, transform and moduleParsed hooks that would be triggered if the module were imported by another module.

这允许你在决定如何在 resolveId 钩子中解析模块之前检查模块的最终内容,例如 改为解析为代理模块。 如果模块稍后成为图的一部分,则使用此上下文函数不会产生额外的开销,因为该模块将不会被再次解析。 该签名允许你直接将 this.resolve 的返回值传递给此函数,只要它既不是 null 也不是外部的。

This allows you to inspect the final content of modules before deciding how to resolve them in the resolveId hook and e.g. resolve to a proxy module instead. If the module becomes part of the graph later, there is no additional overhead from using this context function as the module will not be parsed again. The signature allows you to directly pass the return value of this.resolve to this function as long as it is neither null nor external.

一旦模块完全转换和解析,但在解决任何导入之前,返回的 Promise 将解析。 这意味着生成的 ModuleInfo 将具有空的 importedIdsdynamicallyImportedIdsimportedIdResolutionsdynamicallyImportedIdResolutions。 这有助于避免在 resolveId 钩子中等待 this.load 时出现死锁情况。 如果你对 importedIdsdynamicallyImportedIds 感兴趣,你可以实现 moduleParsed 钩子或传递 resolveDependencies 标志,这将使 this.load 返回的 Promise 等待,直到所有依赖 ID 都已解析。

The returned Promise will resolve once the module has been fully transformed and parsed but before any imports have been resolved. That means that the resulting ModuleInfo will have empty importedIds, dynamicallyImportedIds, importedIdResolutions and dynamicallyImportedIdResolutions. This helps to avoid deadlock situations when awaiting this.load in a resolveId hook. If you are interested in importedIds and dynamicallyImportedIds, you can either implement a moduleParsed hook or pass the resolveDependencies flag, which will make the Promise returned by this.load wait until all dependency ids have been resolved.

请注意,对于 attributesmetamoduleSideEffectssyntheticNamedExports 选项,适用与 resolveId 钩子相同的限制: 它们的值仅在模块尚未加载时才有效。 因此,首先使用 this.resolve 来查明是否有任何插件想要在其 resolveId 钩子中为这些选项设置特殊值,然后在适当的情况下将这些选项传递给 this.load 是非常重要的。 下面的示例展示了如何处理此问题,为包含特殊代码注释的模块添加代理模块。 请注意重新导出默认导出的特殊处理:

Note that with regard to the attributes, meta, moduleSideEffects and syntheticNamedExports options, the same restrictions apply as for the resolveId hook: Their values only have an effect if the module has not been loaded yet. Thus, it is very important to use this.resolve first to find out if any plugins want to set special values for these options in their resolveId hook, and pass these options on to this.load if appropriate. The example below showcases how this can be handled to add a proxy module for modules containing a special code comment. Note the special handling for re-exporting the default export:

js
export default function addProxyPlugin() {
	return {
		async resolveId(source, importer, options) {
			if (importer?.endsWith('?proxy')) {
				// Do not proxy ids used in proxies
				return null;
			}
			// We make sure to pass on any resolveId options to
			// this.resolve to get the module id
			const resolution = await this.resolve(source, importer, options);
			// We can only pre-load existing and non-external ids
			if (resolution && !resolution.external) {
				// we pass on the entire resolution information
				const moduleInfo = await this.load(resolution);
				if (moduleInfo.code.includes('/* use proxy */')) {
					return `${resolution.id}?proxy`;
				}
			}
			// As we already fully resolved the module, there is no reason
			// to resolve it again
			return resolution;
		},
		load(id) {
			if (id.endsWith('?proxy')) {
				const importee = id.slice(0, -'?proxy'.length);
				// Note that namespace reexports do not reexport default
				// exports
				let code = `console.log('proxy for ${importee}'); export * from ${JSON.stringify(
					importee
				)};`;
				// We know that while resolving the proxy, importee was
				// already fully loaded and parsed, so we can rely on
				// hasDefaultExport
				if (this.getModuleInfo(importee).hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(importee)};`;
				}
				return code;
			}
			return null;
		}
	};
}
export default function addProxyPlugin() {
	return {
		async resolveId(source, importer, options) {
			if (importer?.endsWith('?proxy')) {
				// Do not proxy ids used in proxies
				return null;
			}
			// We make sure to pass on any resolveId options to
			// this.resolve to get the module id
			const resolution = await this.resolve(source, importer, options);
			// We can only pre-load existing and non-external ids
			if (resolution && !resolution.external) {
				// we pass on the entire resolution information
				const moduleInfo = await this.load(resolution);
				if (moduleInfo.code.includes('/* use proxy */')) {
					return `${resolution.id}?proxy`;
				}
			}
			// As we already fully resolved the module, there is no reason
			// to resolve it again
			return resolution;
		},
		load(id) {
			if (id.endsWith('?proxy')) {
				const importee = id.slice(0, -'?proxy'.length);
				// Note that namespace reexports do not reexport default
				// exports
				let code = `console.log('proxy for ${importee}'); export * from ${JSON.stringify(
					importee
				)};`;
				// We know that while resolving the proxy, importee was
				// already fully loaded and parsed, so we can rely on
				// hasDefaultExport
				if (this.getModuleInfo(importee).hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(importee)};`;
				}
				return code;
			}
			return null;
		}
	};
}

如果模块已经加载,this.load 将等待解析完成,然后返回其模块信息。 如果该模块尚未被其他模块导入,则不会自动触发加载该模块导入的其他模块。 相反,只有在该模块实际导入至少一次后,才会加载静态和动态依赖。

If the module was already loaded, this.load will just wait for the parsing to complete and then return its module information. If the module was not yet imported by another module, it will not automatically trigger loading other modules imported by this module. Instead, static and dynamic dependencies will only be loaded once this module has actually been imported at least once.

虽然在 resolveId 钩子中使用 this.load 是安全的,但在 loadtransform 钩子中等待它时应该非常小心。 如果模块图中存在循环依赖,这很容易导致死锁,因此任何插件都需要手动注意避免在与加载的模块处于循环的任何模块的 loadtransform 内等待 this.load

While it is safe to use this.load in a resolveId hook, you should be very careful when awaiting it in a load or transform hook. If there are cyclic dependencies in the module graph, this can easily lead to a deadlock, so any plugin needs to manually take care to avoid waiting for this.load inside the load or transform of the any module that is in a cycle with the loaded module.

这是另一个更详细的示例,其中我们通过 resolveDependencies 选项扫描整个依赖子图并重复调用 this.load。 我们使用 Set 个已处理模块 ID 来处理循环依赖。 该插件的目标是向每个动态导入的块添加一个日志,仅列出该块中的所有模块。 虽然这只是一个玩具示例,但该技术可用于例如 为子图中导入的所有 CSS 创建单个样式标签。

Here is another, more elaborate example where we scan entire dependency sub-graphs via the resolveDependencies option and repeated calls to this.load. We use a Set of handled module ids to handle cyclic dependencies. The goal of the plugin is to add a log to each dynamically imported chunk that just lists all modules in the chunk. While this is just a toy example, the technique could be used to e.g. create a single style tag for all CSS imported in the sub-graph.

js
// The leading \0 instructs other plugins not to try to resolve, load or
// transform our proxy modules
const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:';

export default function dynamicChunkLogsPlugin() {
	return {
		name: 'dynamic-chunk-logs',
		async resolveDynamicImport(specifier, importer) {
			// Ignore non-static targets
			if (!(typeof specifier === 'string')) return;
			// Get the id and initial meta information of the import target
			const resolved = await this.resolve(specifier, importer);
			// Ignore external targets. Explicit externals have the
			// "external" property while unresolved imports are "null".
			if (resolved && !resolved.external) {
				// We trigger loading the module without waiting for it
				// here because meta information attached by resolveId
				// hooks, that may be contained in "resolved" and that
				// plugins like "commonjs" may depend upon, is only
				// attached to a module the first time it is loaded. This
				// ensures that this meta information is not lost when we
				// later use "this.load" again in the load hook with just
				// the module id.
				this.load(resolved);
				return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`;
			}
		},
		async load(id) {
			// Ignore all files except our dynamic import proxies
			if (!id.startsWith('\0dynamic-import:')) return null;
			const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length);
			// To allow loading modules in parallel while keeping
			// complexity low, we do not directly await each "this.load"
			// call but put their promises into an array where we await
			// them via an async for loop.
			const moduleInfoPromises = [
				this.load({ id: actualId, resolveDependencies: true })
			];
			// We track each loaded dependency here so that we do not load
			// a file twice and also do  not get stuck when there are
			// circular dependencies.
			const dependencies = new Set([actualId]);
			// "importedIdResolutions" tracks the objects created by
			// resolveId hooks. We are using those instead of "importedIds"
			// so that again, important meta information is not lost.
			for await (const { importedIdResolutions } of moduleInfoPromises) {
				for (const resolved of importedIdResolutions) {
					if (!dependencies.has(resolved.id)) {
						dependencies.add(resolved.id);
						moduleInfoPromises.push(
							this.load({ ...resolved, resolveDependencies: true })
						);
					}
				}
			}
			// We log all modules in a dynamic chunk when it is loaded.
			let code = `console.log([${[...dependencies]
				.map(JSON.stringify)
				.join(', ')}]); export * from ${JSON.stringify(actualId)};`;
			// Namespace reexports do not reexport default exports, which
			// is why we reexport it manually if it exists
			if (this.getModuleInfo(actualId).hasDefaultExport) {
				code += `export { default } from ${JSON.stringify(actualId)};`;
			}
			return code;
		}
	};
}
// The leading \0 instructs other plugins not to try to resolve, load or
// transform our proxy modules
const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:';

export default function dynamicChunkLogsPlugin() {
	return {
		name: 'dynamic-chunk-logs',
		async resolveDynamicImport(specifier, importer) {
			// Ignore non-static targets
			if (!(typeof specifier === 'string')) return;
			// Get the id and initial meta information of the import target
			const resolved = await this.resolve(specifier, importer);
			// Ignore external targets. Explicit externals have the
			// "external" property while unresolved imports are "null".
			if (resolved && !resolved.external) {
				// We trigger loading the module without waiting for it
				// here because meta information attached by resolveId
				// hooks, that may be contained in "resolved" and that
				// plugins like "commonjs" may depend upon, is only
				// attached to a module the first time it is loaded. This
				// ensures that this meta information is not lost when we
				// later use "this.load" again in the load hook with just
				// the module id.
				this.load(resolved);
				return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`;
			}
		},
		async load(id) {
			// Ignore all files except our dynamic import proxies
			if (!id.startsWith('\0dynamic-import:')) return null;
			const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length);
			// To allow loading modules in parallel while keeping
			// complexity low, we do not directly await each "this.load"
			// call but put their promises into an array where we await
			// them via an async for loop.
			const moduleInfoPromises = [
				this.load({ id: actualId, resolveDependencies: true })
			];
			// We track each loaded dependency here so that we do not load
			// a file twice and also do  not get stuck when there are
			// circular dependencies.
			const dependencies = new Set([actualId]);
			// "importedIdResolutions" tracks the objects created by
			// resolveId hooks. We are using those instead of "importedIds"
			// so that again, important meta information is not lost.
			for await (const { importedIdResolutions } of moduleInfoPromises) {
				for (const resolved of importedIdResolutions) {
					if (!dependencies.has(resolved.id)) {
						dependencies.add(resolved.id);
						moduleInfoPromises.push(
							this.load({ ...resolved, resolveDependencies: true })
						);
					}
				}
			}
			// We log all modules in a dynamic chunk when it is loaded.
			let code = `console.log([${[...dependencies]
				.map(JSON.stringify)
				.join(', ')}]); export * from ${JSON.stringify(actualId)};`;
			// Namespace reexports do not reexport default exports, which
			// is why we reexport it manually if it exists
			if (this.getModuleInfo(actualId).hasDefaultExport) {
				code += `export { default } from ${JSON.stringify(actualId)};`;
			}
			return code;
		}
	};
}

this.meta

类型:{rollupVersion: string, watchMode: boolean}

包含潜在有用的 Rollup 元数据的对象:

An object containing potentially useful Rollup metadata:

  • rollupVersion: 当前运行的 Rollup 版本,如 package.json 中定义。
  • watchMode: 如果 Rollup 是通过 rollup.watch(...) 或从命令行启动的,则为 true,否则为 --watchfalse

meta 是唯一可通过 options 钩子访问的上下文属性。

meta is the only context property accessible from the options hook.

this.parse

类型:(code: string, options?: ParseOptions) => ESTree.Program
typescript
interface ParseOptions {
	allowReturnOutsideFunction?: boolean;
}
interface ParseOptions {
	allowReturnOutsideFunction?: boolean;
}

使用 Rollup 内部基于 SWC 的解析器将代码解析为 ESTree-compatible AST。

Use Rollup's internal SWC-based parser to parse code to an ESTree-compatible AST.

  • allowReturnOutsideFunction: 当 true 时,这允许 return 语句位于函数之外,例如 支持解析 CommonJS 代码。

this.resolve

类型:Resolve
typescript
type Resolve = (
	source: string,
	importer?: string,
	options?: {
		skipSelf?: boolean;
		isEntry?: boolean;
		attributes?: { [key: string]: string };
		custom?: { [plugin: string]: any };
	}
) => ResolvedId;
type Resolve = (
	source: string,
	importer?: string,
	options?: {
		skipSelf?: boolean;
		isEntry?: boolean;
		attributes?: { [key: string]: string };
		custom?: { [plugin: string]: any };
	}
) => ResolvedId;

提示

该钩子的返回类型 ResolvedIdthis.getModuleInfo 中定义。

The return type ResolvedId of this hook is defined in this.getModuleInfo.

使用 Rollup 使用的相同插件解析模块 ID(即文件名)的导入,并确定导入是否应该是外部的。 如果返回 null,则导入无法通过 Rollup 或任何插件解析,但用户未明确标记为外部。 如果返回的绝对外部 id 应通过 makeAbsoluteExternalsRelative 选项或通过 resolveId 钩子中的显式插件选择在输出中保持绝对,则 external 将是 "absolute" 而不是 true

Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses, and determine if an import should be external. If null is returned, the import could not be resolved by Rollup or any plugin but was not explicitly marked as external by the user. If an absolute external id is returned that should remain absolute in the output either via the makeAbsoluteExternalsRelative option or by explicit plugin choice in the resolveId hook, external will be "absolute" instead of true.

skipSelf 的默认值是 true,因此解析时将跳过调用 this.resolve 的插件的 resolveId 钩子。 当其他插件本身在处理原始 this.resolve 调用时也在其 resolveId 钩子中使用完全相同的 sourceimporter 调用 this.resolve 时,那么原始插件的 resolveId 钩子也将被跳过这些调用。 这里的基本原理是,插件已经声明 "不知道" 如何在此时解决 sourceimporter 的特定组合。 如果你不希望出现这种行为,请将 skipSelf 设置为 false,并在必要时实现你自己的无限循环预防机制。

The default of skipSelf is true, So the resolveId hook of the plugin from which this.resolve is called will be skipped when resolving. When other plugins themselves also call this.resolve in their resolveId hooks with the exact same source and importer while handling the original this.resolve call, then the resolveId hook of the original plugin will be skipped for those calls as well. The rationale here is that the plugin already stated that it "does not know" how to resolve this particular combination of source and importer at this point in time. If you do not want this behaviour, set skipSelf to false and implement your own infinite loop prevention mechanism if necessary.

你还可以通过 custom 选项传递插件特定选项的对象,有关详细信息,请参阅 自定义解析器选项

You can also pass an object of plugin-specific options via the custom option, see custom resolver options for details.

你在此处传递的 isEntry 的值将传递到处理此调用的 resolveId 钩子,否则,如果有导入程序,则将传递 false;如果没有,则将传递 true

The value for isEntry you pass here will be passed along to the resolveId hooks handling this call, otherwise false will be passed if there is an importer and true if there is not.

如果你传递 attributes 的对象,它将模拟使用断言解析导入,例如 attributes: {type: "json"} 模拟解析 import "foo" assert {type: "json"}。 这将被传递给任何处理此调用的 resolveId 钩子,并可能最终成为返回对象的一部分。

If you pass an object for attributes, it will simulate resolving an import with an assertion, e.g. attributes: {type: "json"} simulates resolving import "foo" assert {type: "json"}. This will be passed to any resolveId hooks handling this call and may ultimately become part of the returned object.

resolveId 钩子调用此函数时,你应该始终检查传递 isEntrycustomattributes 选项是否有意义。

When calling this function from a resolveId hook, you should always check if it makes sense for you to pass along the isEntry, custom and attributes options.

resolvedBy 的值指的是哪个插件解析了这个源。 如果是 Rollup 自己解决的,则该值为 "Rollup"。 如果插件中的 resolveId 钩子解析此源,则该值将是插件的名称,除非它返回 resolvedBy 的显式值。 该标志仅用于调试和文档目的,不会由 Rollup 进一步处理。

The value of resolvedBy refers to which plugin resolved this source. If it was resolved by Rollup itself, the value will be "rollup". If a resolveId hook in a plugin resolves this source, the value will be the name of the plugin unless it returned an explicit value for resolvedBy. This flag is only for debugging and documentation purposes and is not processed further by Rollup.

this.setAssetSource

类型:(referenceId: string, source: string | Uint8Array) => void

设置资源的延迟来源。 请注意,你还可以将节点 Buffer 作为 source 传递,因为它是 Uint8Array 的子类。

Set the deferred source of an asset. Note that you can also pass a Node Buffer as source as it is a sub-class of Uint8Array.

this.warn

类型:(log: string | RollupLog | (() => RollupLog | string), position?: number | { column: number; line: number }) => void

使用此方法将生成构建警告,这些警告是日志级别 "warn" 的日志。 有关 RollupLog 类型的信息,请参阅 onLog 选项。 要生成其他日志,另请参阅 this.infothis.debug。 要生成错误,请参阅 this.error

Using this method will generate warnings for a build, which are logs with log level "warn". See the onLog option for information about the RollupLog type. To generate other logs, see also this.info and this.debug. To generate errors, see this.error.

就像内部生成的警告一样,这些日志将首先传递到插件 onLog 钩子并由其过滤,然后再转发到自定义 onLogonwarn 处理程序或打印到控制台。

Just like internally generated warnings, these logs will be first passed to and filtered by plugin onLog hooks before they are forwarded to custom onLog or onwarn handlers or printed to the console.

warning 参数可以是 string 或具有(至少)message 属性的对象:

The warning argument can be a string or an object with (at minimum) a message property:

js
this.warn('hmm...');
// is equivalent to
this.warn({
	message: 'hmm...',
	pluginCode: 'CODE_TO_IDENTIFY_LOG',
	meta: 'Additional plugin specific information'
});
this.warn('hmm...');
// is equivalent to
this.warn({
	message: 'hmm...',
	pluginCode: 'CODE_TO_IDENTIFY_LOG',
	meta: 'Additional plugin specific information'
});

我们鼓励你使用具有 pluginCode 属性的对象,因为这将允许用户在 onLog 处理程序中轻松过滤这些日志。 如果需要添加其他信息,可以使用 meta 属性。 如果日志包含 code 并且还没有 pluginCode 属性,它将被重命名为 pluginCode,因为插件警告始终会获得由 Rollup 添加的 codePLUGIN_WARNING。 为了防止这种行为,插件可以使用传递给 buildStart 钩子的规范化 onLog 选项。 将日志传递给插件 onLog 处理程序和 onLogonwarn 处理程序时,从插件调用此选项不会更改属性。

We encourage you to use objects with a pluginCode property as that will allow users to easily filter for those logs in an onLog handler. If you need to add additional information, you can use the meta property. If the log contains a code and does not yet have a pluginCode property, it will be renamed to pluginCode as plugin warnings always get a code of PLUGIN_WARNING added by Rollup. To prevent this behavior, plugins can instead use the normalized onLog option passed to the buildStart hook. Calling this option from a plugin will not change properties when passing the log to plugin onLog handlers and onLog or onwarn handlers.

如果你需要进行昂贵的计算来生成日志,你还可以传递一个返回 stringRollupLog 对象的函数。 仅当日志未通过 logLevel 选项过滤时才会调用此函数。

If you need to do expensive computations to generate a log, you can also pass a function returning either a string or a RollupLog object. This function will only be called if the log is not filtered by the logLevel option.

js
// This will only run if the logLevel is set to "debug"
this.debug(() => generateExpensiveDebugLog());
// This will only run if the logLevel is set to "debug"
this.debug(() => generateExpensiveDebugLog());

当在 transform 钩子中使用时,当前模块的 id 也会被添加,并且可以提供 position。 这是一个字符索引或文件位置,将用于使用 posloc(标准 { file, line, column } 对象)和 frame(显示位置的代码片段)来扩充日志。

When used in the transform hook, the id of the current module will also be added and a position can be supplied. This is a character index or file location which will be used to augment the log with pos, loc (a standard { file, line, column } object) and frame (a snippet of code showing the location).

如果 logLevel 选项设置为 "silent",则此方法将不执行任何操作。

If the logLevel option is set to "silent", this method will do nothing.

文件网址

要从 JS 代码中引用文件 URL 引用,请使用 import.meta.ROLLUP_FILE_URL_referenceId 替换。 这将生成取决于输出格式的代码,并生成指向目标环境中触发的文件的 URL。 请注意,除 CommonJS 和 UMD 之外的所有格式都假定它们在 URLdocument 可用的浏览器环境中运行。

To reference a file URL reference from within JS code, use the import.meta.ROLLUP_FILE_URL_referenceId replacement. This will generate code that depends on the output format and generates a URL that points to the emitted file in the target environment. Note that all formats except CommonJS and UMD assume that they run in a browser environment where URL and document are available.

以下示例将检测 .svg 文件的导入,将导入的文件作为资源触发,并返回要使用的 URL,例如 作为 img 标签的 src 属性:

The following example will detect imports of .svg files, emit the imported files as assets, and return their URLs to be used e.g. as the src attribute of an img tag:

js
function svgResolverPlugin() {
	return {
		name: 'svg-resolver',
		resolveId(source, importer) {
			if (source.endsWith('.svg')) {
				return path.resolve(path.dirname(importer), source);
			}
		},
		load(id) {
			if (id.endsWith('.svg')) {
				const referenceId = this.emitFile({
					type: 'asset',
					name: path.basename(id),
					source: fs.readFileSync(id)
				});
				return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
			}
		}
	};
}
function svgResolverPlugin() {
	return {
		name: 'svg-resolver',
		resolveId(source, importer) {
			if (source.endsWith('.svg')) {
				return path.resolve(path.dirname(importer), source);
			}
		},
		load(id) {
			if (id.endsWith('.svg')) {
				const referenceId = this.emitFile({
					type: 'asset',
					name: path.basename(id),
					source: fs.readFileSync(id)
				});
				return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
			}
		}
	};
}

用法:

Usage:

js
import logo from '../images/logo.svg';
const image = document.createElement('img');
image.src = logo;
document.body.appendChild(image);
import logo from '../images/logo.svg';
const image = document.createElement('img');
image.src = logo;
document.body.appendChild(image);

有时,引用此资源的代码仅有条件地使用,如下例所示:

Sometimes, the code which referenced this asset is only used conditionally like in the following example:

js
import logo from '../images/logo.svg';
if (COMPILER_FLAG) {
	const image = document.createElement('img');
	image.src = logo;
	document.body.appendChild(image);
}
import logo from '../images/logo.svg';
if (COMPILER_FLAG) {
	const image = document.createElement('img');
	image.src = logo;
	document.body.appendChild(image);
}

如果一个插件将 COMPILER_FLAG 替换为 false,那么我们会得到意想不到的结果: 未引用的资源仍会触发但未使用。 我们可以通过在调用 this.emitFile 时将 needsCodeReference 设置为 true 来解决这个问题,如下代码:

If a plugin replaces COMPILER_FLAG with false, then we will get an unexpected result: The unreferenced asset is still emitted but unused. We can resolve this problem by setting needsCodeReference to true when calling this.emitFile, like in the following code:

js
function svgResolverPlugin() {
	return {
		/* ... */
		load(id) {
			if (id.endsWith('.svg')) {
				const referenceId = this.emitFile({
					type: 'asset',
					name: path.basename(id),
					needsCodeReference: true,
					source: fs.readFileSync(id)
				});
				return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
			}
		}
	};
}
function svgResolverPlugin() {
	return {
		/* ... */
		load(id) {
			if (id.endsWith('.svg')) {
				const referenceId = this.emitFile({
					type: 'asset',
					name: path.basename(id),
					needsCodeReference: true,
					source: fs.readFileSync(id)
				});
				return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
			}
		}
	};
}

现在,只有在代码中实际使用引用 import.meta.ROLLUP_FILE_URL_referenceId 时,资源才会添加到打包包中。

Now the asset will only be added to the bundle if the reference import.meta.ROLLUP_FILE_URL_referenceId is actually used in the code.

与资源类似,触发的块也可以通过 import.meta.ROLLUP_FILE_URL_referenceId 从 JS 代码中引用。

Similar to assets, emitted chunks can be referenced from within JS code via import.meta.ROLLUP_FILE_URL_referenceId as well.

以下示例将检测以 register-paint-worklet: 为前缀的导入,并生成必要的代码和单独的块以生成 CSS 绘制工作集。 请注意,这仅适用于现代浏览器,并且仅在输出格式设置为 es 时才有效。

The following example will detect imports prefixed with register-paint-worklet: and generate the necessary code and separate chunk to generate a CSS paint worklet. Note that this will only work in modern browsers and will only work if the output format is set to es.

js
const REGISTER_WORKLET = 'register-paint-worklet:';

function registerPaintWorkletPlugin() {
	return {
		name: 'register-paint-worklet',
		load(id) {
			if (id.startsWith(REGISTER_WORKLET)) {
				return `CSS.paintWorklet.addModule(import.meta.ROLLUP_FILE_URL_${this.emitFile(
					{
						type: 'chunk',
						id: id.slice(REGISTER_WORKLET.length)
					}
				)});`;
			}
		},
		resolveId(source, importer) {
			// We remove the prefix, resolve everything to absolute ids and
			// add the prefix again. This makes sure that you can use
			// relative imports to define worklets
			if (source.startsWith(REGISTER_WORKLET)) {
				return this.resolve(
					source.slice(REGISTER_WORKLET.length),
					importer
				).then(resolvedId => REGISTER_WORKLET + resolvedId.id);
			}
			return null;
		}
	};
}
const REGISTER_WORKLET = 'register-paint-worklet:';

function registerPaintWorkletPlugin() {
	return {
		name: 'register-paint-worklet',
		load(id) {
			if (id.startsWith(REGISTER_WORKLET)) {
				return `CSS.paintWorklet.addModule(import.meta.ROLLUP_FILE_URL_${this.emitFile(
					{
						type: 'chunk',
						id: id.slice(REGISTER_WORKLET.length)
					}
				)});`;
			}
		},
		resolveId(source, importer) {
			// We remove the prefix, resolve everything to absolute ids and
			// add the prefix again. This makes sure that you can use
			// relative imports to define worklets
			if (source.startsWith(REGISTER_WORKLET)) {
				return this.resolve(
					source.slice(REGISTER_WORKLET.length),
					importer
				).then(resolvedId => REGISTER_WORKLET + resolvedId.id);
			}
			return null;
		}
	};
}

用法:

Usage:

js
// main.js
import 'register-paint-worklet:./worklet.js';
import { color, size } from './config.js';
document.body.innerHTML += `<h1 style="background-image: paint(vertical-lines);">color: ${color}, size: ${size}</h1>`;

// worklet.js
import { color, size } from './config.js';
registerPaint(
	'vertical-lines',
	class {
		paint(ctx, geom) {
			for (let x = 0; x < geom.width / size; x++) {
				ctx.beginPath();
				ctx.fillStyle = color;
				ctx.rect(x * size, 0, 2, geom.height);
				ctx.fill();
			}
		}
	}
);

// config.js
export const color = 'greenyellow';
export const size = 6;
// main.js
import 'register-paint-worklet:./worklet.js';
import { color, size } from './config.js';
document.body.innerHTML += `<h1 style="background-image: paint(vertical-lines);">color: ${color}, size: ${size}</h1>`;

// worklet.js
import { color, size } from './config.js';
registerPaint(
	'vertical-lines',
	class {
		paint(ctx, geom) {
			for (let x = 0; x < geom.width / size; x++) {
				ctx.beginPath();
				ctx.fillStyle = color;
				ctx.rect(x * size, 0, 2, geom.height);
				ctx.fill();
			}
		}
	}
);

// config.js
export const color = 'greenyellow';
export const size = 6;

如果你构建此代码,主块和工作集都将通过共享块共享 config.js 中的代码。 这使我们能够利用浏览器缓存来减少传输的数据并加快工作集的加载速度。

If you build this code, both the main chunk and the worklet will share the code from config.js via a shared chunk. This enables us to make use of the browser cache to reduce transmitted data and speed up loading the worklet.

转换器

Transformer 插件(即那些返回 transform 函数的插件,例如用于转译非 JS 文件的插件)应该支持 options.includeoptions.exclude,这两个插件都可以是迷你匹配模式或迷你匹配模式数组。 如果省略 options.include 或长度为零,则默认包含文件; 否则,仅当 ID 与其中一种模式匹配时才应包含它们。

Transformer plugins (i.e. those that return a transform function for e.g. transpiling non-JS files) should support options.include and options.exclude, both of which can be a minimatch pattern or an array of minimatch patterns. If options.include is omitted or of zero length, files should be included by default; otherwise they should only be included if the ID matches one of the patterns.

transform 钩子如果返回对象,还可以包含 ast 属性。 仅当你知道自己在做什么时才使用此功能。 请注意,只会使用转换链中的最后一个 AST(如果存在转换,则对于转换后的模块,load 钩子生成的任何 AST 都将被丢弃。)

The transform hook, if returning an object, can also include an ast property. Only use this feature if you know what you're doing. Note that only the last AST in a chain of transforms will be used (and if there are transforms, any ASTs generated by the load hook will be discarded for the transformed modules.)

转换器示例

(使用 @rollup/pluginutils 来实现常用功能,并以推荐的方式实现转换器。)

(Use @rollup/pluginutils for commonly needed functions, and to implement a transformer in the recommended manner.)

js
import { createFilter } from '@rollup/pluginutils';

function transformCodePlugin(options = {}) {
	const filter = createFilter(options.include, options.exclude);

	return {
		name: 'transform-code',
		transform(code, id) {
			if (!filter(id)) return;

			// proceed with the transformation...
			return {
				code: generatedCode,
				map: generatedSourceMap
			};
		}
	};
}
import { createFilter } from '@rollup/pluginutils';

function transformCodePlugin(options = {}) {
	const filter = createFilter(options.include, options.exclude);

	return {
		name: 'transform-code',
		transform(code, id) {
			if (!filter(id)) return;

			// proceed with the transformation...
			return {
				code: generatedCode,
				map: generatedSourceMap
			};
		}
	};
}

源代码转换

如果插件转换源代码,它应该自动生成源映射,除非有特定的 sourceMap: false 选项。 Rollup 只关心 mappings 属性(其他所有内容都会自动处理)。 魔术弦 提供了一种简单的方法来生成此类映射以进行基本转换(例如添加或删除代码片段)。

If a plugin transforms source code, it should generate a sourcemap automatically, unless there's a specific sourceMap: false option. Rollup only cares about the mappings property (everything else is handled automatically). magic-string provides a simple way to generate such a map for elementary transformations like adding or removing code snippets.

如果生成源映射没有意义(例如 rollup-plugin-string),则返回空源映射:

If it doesn't make sense to generate a sourcemap, (e.g. rollup-plugin-string), return an empty sourcemap:

js
return {
	code: transformedCode,
	map: { mappings: '' }
};
return {
	code: transformedCode,
	map: { mappings: '' }
};

如果转换不移动代码,你可以通过返回 null 来保留现有的源映射:

If the transformation does not move code, you can preserve existing sourcemaps by returning null:

js
return {
	code: transformedCode,
	map: null
};
return {
	code: transformedCode,
	map: null
};

如果你创建了一个你认为对其他人有用的插件,请将其发布到 NPM 并添加提交到 github.com/rollup/awesome

If you create a plugin that you think would be useful to others, please publish it to NPM and add submit it to github.com/rollup/awesome!

综合命名导出

通过在 resolveIdloadtransform 钩子中为模块设置 syntheticNamedExports 选项,可以为丢失的导出指定后备导出。 如果字符串值用于 syntheticNamedExports,则此模块会将任何缺失的命名导出的解析回退到给定名称的命名导出的属性:

It is possible to designate a fallback export for missing exports by setting the syntheticNamedExports option for a module in the resolveId, load or transform hook. If a string value is used for syntheticNamedExports, this module will fallback the resolution of any missing named exports to properties of the named export of the given name:

dep.js:({syntheticNamedExports: 'synthetic'}

dep.js: ({syntheticNamedExports: '__synthetic'})

js
export const foo = 'explicit';
export const __synthetic = {
	foo: 'foo',
	bar: 'bar'
};
export const foo = 'explicit';
export const __synthetic = {
	foo: 'foo',
	bar: 'bar'
};

main.js:

main.js:

js
import { foo, bar, baz, __synthetic } from './dep.js';

// logs "explicit" as non-synthetic exports take precedence
console.log(foo);

// logs "bar", picking the property from __synthetic
console.log(bar);

// logs "undefined"
console.log(baz);

// logs "{foo:'foo',bar:'bar'}"
console.log(__synthetic);
import { foo, bar, baz, __synthetic } from './dep.js';

// logs "explicit" as non-synthetic exports take precedence
console.log(foo);

// logs "bar", picking the property from __synthetic
console.log(bar);

// logs "undefined"
console.log(baz);

// logs "{foo:'foo',bar:'bar'}"
console.log(__synthetic);

当用作入口点时,只会公开显式导出。 合成后备导出(即示例中的 __synthetic)不会针对 syntheticNamedExports 的字符串值公开。 但是,如果值为 true,则会暴露默认导出。 这是 syntheticNamedExports: truesyntheticNamedExports: 'default' 之间唯一显着的区别。

When used as an entry point, only explicit exports will be exposed. The synthetic fallback export, i.e. __synthetic in the example, will not be exposed for string values of syntheticNamedExports. However, if the value is true, the default export will be exposed. This is the only notable difference between syntheticNamedExports: true and syntheticNamedExports: 'default'.

插件间通信

在使用许多专用插件时的某些时候,可能需要不相关的插件能够在构建过程中交换信息。 Rollup 通过多种机制使这成为可能。

At some point when using many dedicated plugins, there may be the need for unrelated plugins to be able to exchange information during the build. There are several mechanisms through which Rollup makes this possible.

自定义解析器选项

假设你有一个插件,该插件应根据另一个插件生成导入的方式来解析对不同 id 的导入。 实现此目的的一种方法是重写导入以使用特殊的代理 ID,例如 CommonJS 文件中通过 require("foo") 进行的转译导入可以成为具有特殊 id import "foo?require=true" 的常规导入,以便解析器插件知道这一点。

Assume you have a plugin that should resolve an import to different ids depending on how the import was generated by another plugin. One way to achieve this would be to rewrite the import to use special proxy ids, e.g. a transpiled import via require("foo") in a CommonJS file could become a regular import with a special id import "foo?require=true" so that a resolver plugin knows this.

然而,这里的问题是,这个代理 ID 在传递给其他解析器时可能会也可能不会导致意外的副作用,因为它并不真正对应于文件。 此外,如果 id 是由插件 A 创建的,并且解析发生在插件 B 中,则会在这些插件之间创建依赖,以便在没有 B 的情况下 A 无法使用。

The problem here, however, is that this proxy id may or may not cause unintended side effects when passed to other resolvers because it does not really correspond to a file. Moreover, if the id is created by plugin A and the resolution happens in plugin B, it creates a dependency between these plugins so that A is not usable without B.

自定义解析器选项在这里提供了一个解决方案,允许在通过 this resolve 手动解析模块时传递插件的附加选项。 发生这种情况时不会更改 id,因此如果预期的目标插件不存在,也不会影响其他插件正确解析模块的能力。

Custom resolver option offer a solution here by allowing to pass additional options for plugins when manually resolving a module via this resolve. This happens without changing the id and thus without impairing the ability for other plugins to resolve the module correctly if the intended target plugin is not present.

js
function requestingPlugin() {
	return {
		name: 'requesting',
		async buildStart() {
			const resolution = await this.resolve('foo', undefined, {
				custom: { resolving: { specialResolution: true } }
			});
			console.log(resolution.id); // "special"
		}
	};
}

function resolvingPlugin() {
	return {
		name: 'resolving',
		resolveId(id, importer, { custom }) {
			if (custom.resolving?.specialResolution) {
				return 'special';
			}
			return null;
		}
	};
}
function requestingPlugin() {
	return {
		name: 'requesting',
		async buildStart() {
			const resolution = await this.resolve('foo', undefined, {
				custom: { resolving: { specialResolution: true } }
			});
			console.log(resolution.id); // "special"
		}
	};
}

function resolvingPlugin() {
	return {
		name: 'resolving',
		resolveId(id, importer, { custom }) {
			if (custom.resolving?.specialResolution) {
				return 'special';
			}
			return null;
		}
	};
}

请注意,应使用与解析插件的插件名称相对应的属性来添加自定义选项。 解析插件有责任指定它尊重哪些选项。

Note the convention that custom options should be added using a property corresponding to the plugin name of the resolving plugin. It is responsibility of the resolving plugin to specify which options it respects.

自定义模块元数据

插件可以使用自定义元数据来注释模块,这些元数据可以由它们自己和其他插件通过 resolveIdloadtransform 钩子设置,并通过 this.getModuleInfothis.loadmoduleParsed 钩子访问。 此元数据应始终为 JSON.stringifyable 并将保留在缓存中,例如 在监视模式下。

Plugins can annotate modules with custom meta-data which can be set by themselves and other plugins via the resolveId, load, and transform hooks and accessed via this.getModuleInfo, this.load and the moduleParsed hook. This meta-data should always be JSON.stringifyable and will be persisted in the cache e.g. in watch mode.

js
function annotatingPlugin() {
	return {
		name: 'annotating',
		transform(code, id) {
			if (thisModuleIsSpecial(code, id)) {
				return { meta: { annotating: { special: true } } };
			}
		}
	};
}

function readingPlugin() {
	let parentApi;
	return {
		name: 'reading',
		buildEnd() {
			const specialModules = Array.from(this.getModuleIds()).filter(
				id => this.getModuleInfo(id).meta.annotating?.special
			);
			// do something with this list
		}
	};
}
function annotatingPlugin() {
	return {
		name: 'annotating',
		transform(code, id) {
			if (thisModuleIsSpecial(code, id)) {
				return { meta: { annotating: { special: true } } };
			}
		}
	};
}

function readingPlugin() {
	let parentApi;
	return {
		name: 'reading',
		buildEnd() {
			const specialModules = Array.from(this.getModuleIds()).filter(
				id => this.getModuleInfo(id).meta.annotating?.special
			);
			// do something with this list
		}
	};
}

请注意,添加或修改数据的插件应使用与插件名称相对应的属性(在本例中为 annotating)。 另一方面,任何插件都可以通过 this.getModuleInfo 从其他插件读取所有元数据。

Note the convention that plugins that add or modify data should use a property corresponding to the plugin name, in this case annotating. On the other hand, any plugin can read all meta-data from other plugins via this.getModuleInfo.

如果多个插件添加元数据或者元数据添加在不同的钩子中,那么这些 meta 对象将被浅层合并。 这意味着如果插件 first 在 resolveId 钩子中添加 {meta: {first: {resolved: "first"}}},在 load 钩子中添加 {meta: {first: {loaded: "first"}}},而插件 secondtransform 钩子中添加 {meta: {second: {transformed: "second"}}},那么生成的 meta 对象将是 {first: {loaded: "first"}, second: {transformed: "second"}}。 这里 resolveId 钩子的结果将被 load 钩子的结果覆盖,因为插件都将它们存储在其 first 顶层属性下。 另一方面,另一个插件的 transform 数据将放置在它旁边。

If several plugins add meta-data or meta-data is added in different hooks, then these meta objects will be merged shallowly. That means if plugin first adds {meta: {first: {resolved: "first"}}} in the resolveId hook and {meta: {first: {loaded: "first"}}} in the load hook while plugin second adds {meta: {second: {transformed: "second"}}} in the transform hook, then the resulting meta object will be {first: {loaded: "first"}, second: {transformed: "second"}}. Here the result of the resolveId hook will be overwritten by the result of the load hook as the plugin was both storing them under its first top-level property. The transform data of the other plugin on the other hand will be placed next to it.

模块的 meta 对象在 Rollup 开始加载模块时立即创建,并针对模块的每个生命周期钩子进行更新。 如果你存储对此对象的引用,你还可以手动更新它。 要访问尚未加载的模块的元对象,可以通过 this.load 触发其创建并加载模块:

The meta object of a module is created as soon as Rollup starts loading a module and is updated for each lifecycle hook of the module. If you store a reference to this object, you can also update it manually. To access the meta object of a module that has not been loaded yet, you can trigger its creation and loading the module via this.load:

js
function plugin() {
	return {
		name: 'test',
		buildStart() {
			// trigger loading a module. We could also pass an initial
			// "meta" object here, but it would be ignored if the module
			// was already loaded via other means
			this.load({ id: 'my-id' });
			// the module info is now available, we do not need to await
			// this.load
			const meta = this.getModuleInfo('my-id').meta;
			// we can also modify meta manually now
			meta.test = { some: 'data' };
		}
	};
}
function plugin() {
	return {
		name: 'test',
		buildStart() {
			// trigger loading a module. We could also pass an initial
			// "meta" object here, but it would be ignored if the module
			// was already loaded via other means
			this.load({ id: 'my-id' });
			// the module info is now available, we do not need to await
			// this.load
			const meta = this.getModuleInfo('my-id').meta;
			// we can also modify meta manually now
			meta.test = { some: 'data' };
		}
	};
}

直接插件通信

对于任何其他类型的插件间通信,我们推荐以下模式。 请注意,api 永远不会与任何即将推出的插件钩子发生冲突。

For any other kind of inter-plugin communication, we recommend the pattern below. Note that api will never conflict with any upcoming plugin hooks.

js
function parentPlugin() {
	return {
		name: 'parent',
		api: {
			//...methods and properties exposed for other plugins
			doSomething(...args) {
				// do something interesting
			}
		}
		// ...plugin hooks
	};
}

function dependentPlugin() {
	let parentApi;
	return {
		name: 'dependent',
		buildStart({ plugins }) {
			const parentName = 'parent';
			const parentPlugin = plugins.find(
				plugin => plugin.name === parentName
			);
			if (!parentPlugin) {
				// or handle this silently if it is optional
				throw new Error(
					`This plugin depends on the "${parentName}" plugin.`
				);
			}
			// now you can access the API methods in subsequent hooks
			parentApi = parentPlugin.api;
		},
		transform(code, id) {
			if (thereIsAReasonToDoSomething(id)) {
				parentApi.doSomething(id);
			}
		}
	};
}
function parentPlugin() {
	return {
		name: 'parent',
		api: {
			//...methods and properties exposed for other plugins
			doSomething(...args) {
				// do something interesting
			}
		}
		// ...plugin hooks
	};
}

function dependentPlugin() {
	let parentApi;
	return {
		name: 'dependent',
		buildStart({ plugins }) {
			const parentName = 'parent';
			const parentPlugin = plugins.find(
				plugin => plugin.name === parentName
			);
			if (!parentPlugin) {
				// or handle this silently if it is optional
				throw new Error(
					`This plugin depends on the "${parentName}" plugin.`
				);
			}
			// now you can access the API methods in subsequent hooks
			parentApi = parentPlugin.api;
		},
		transform(code, id) {
			if (thereIsAReasonToDoSomething(id)) {
				parentApi.doSomething(id);
			}
		}
	};
}