# resolveId
类型: | ResolveIdHook |
---|---|
类别: | async, first |
上一个钩子: | 如果我们正在解析入口点,则为 buildStart,如果我们正在解析导入,则为 moduleParsed,否则作为 resolveDynamicImport 的后备。此外,此钩子可以通过调用 this.emitFile 来在构建阶段的插件钩子中触发以发出入口点,或随时调用 this.resolve 手动解析 id。 |
下一个钩子: | 如果尚未加载解析的 id,则为 load,否则为 buildEnd。 |
type ResolveIdHook = (
source: string,
importer: string | undefined,
options: {
assertions: Record<string, string>;
custom?: { [plugin: string]: any };
isEntry: boolean;
}
) => ResolveIdResult;
type ResolveIdResult = string | null | false | PartialResolvedId;
interface PartialResolvedId {
id: string;
external?: boolean | 'absolute' | 'relative';
assertions?: Record<string, string> | null;
meta?: { [plugin: string]: any } | null;
moduleSideEffects?: boolean | 'no-treeshake' | null;
resolvedBy?: string | null;
syntheticNamedExports?: boolean | string | null;
}
定义一个自定义解析器。解析器可以用于定位第三方依赖项等。这里的 source 就是导入语句中的导入目标,例如:
import { foo } from '../bar.js';
这个 source 的路径是 "../bar.js".
importer 是导入模块的完全解析的 id。在解析入口点时,importer 通常为 undefined。这里的一个例外是通过 this.emitFile 生成的入口点,这里可以提供一个 importer 参数。
对于这些情况,isEntry 选项将告诉你我们正在解析用户定义的入口点、已发出的块,还是是否为 this.resolve 上下文函数提供了 isEntry 参数。
例如,你可以将其用作为入口点定义自定义代理模块的机制。以下插件将所有入口点代理到注入 polyfill 导入的模块中。
// 我们在 polyfill id 前面加上 \0,
// 以告诉其他插件不要尝试加载或转换它
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) {
// 对于polyfill,必须始终考虑副作用
// 否则使用
// "treeshake.moduleSideEffects: false"
// 这样可能会阻止包含polyfill
return { id: POLYFILL_ID, moduleSideEffects: true };
}
if (options.isEntry) {
// 确定实际的入口点是什么。
// 我们需要“skipSelf”来避免无限循环
const resolution = await this.resolve(source, importer, {
skipSelf: true,
...options
});
// 如果无法解析或是外部引用
// 则直接返回错误
if (!resolution || resolution.external) return resolution;
// 在代理的加载钩子中,我们需要知道入口点是否有默认导出。
// 然而,我们不再拥有完整的“resolution”对象,
// 该对象可能包含来自其他插件的元数据,
// 这些元数据仅在第一次加载时添加。
// 因此,我们在此处触发加载
const moduleInfo = await this.load(resolution);
// 我们需要确保原始入口点中的副作用
// 即使对于 treeshake.moduleSideEffects: false
// 也要被考虑。
// “moduleSideEffects”是 ModuleInfo 上的可写属性。
moduleInfo.moduleSideEffects = true;
// 重要的是,新的入口点不以 \0 开头
// 并且与原始入口点具有相同的目录
// 以避免破坏相对外部引用的生成。
// 同时保留名称并仅在末尾添加一个“?query”
// 确保 preserveModules
// 为此入口点生成原始入口点名称。
return `${resolution.id}${PROXY_SUFFIX}`;
}
return null;
},
load(id) {
if (id === POLYFILL_ID) {
// 用实际的 polyfill 替换
return "console.log('polyfill');";
}
if (id.endsWith(PROXY_SUFFIX)) {
const entryId = id.slice(0, -PROXY_SUFFIX.length);
// 我们知道 ModuleInfo.hasDefaultExport 是可靠的
// 因为我们在 resolveId 中等待了 this.load
const { hasDefaultExport } = this.getModuleInfo(entryId);
let code =
`import ${JSON.stringify(POLYFILL_ID)};` +
`export * from ${JSON.stringify(entryId)};`;
// 命名空间重新导出不会重新导出默认值
// 因此我们需要特殊处理
if (hasDefaultExport) {
code += `export { default } from ${JSON.stringify(entryId)};`;
}
return code;
}
return null;
}
};
}
assertions 参数告诉你导入中存在哪些导入断言。例如,import "foo" assert {type: "json"} 将传递 assertions: {type: "json}"。
如果返回 null,则会转而使用其他 resolveId 函数,最终使用默认的解析行为。如果返回 false,则表示 source 应被视为外部模块,不包含在产物中。如果这发生在相对导入中,则会像使用 external 选项时一样重新规范化 id。
如果返回一个对象,则可以将导入解析为不同的 id,同时将其从产物中排除。这使你可以将依赖项替换为外部依赖项,而无需用户手动通过 external 选项将它们挪到产物“之外” :
function externalizeDependencyPlugin() {
return {
name: 'externalize-dependency',
resolveId(source) {
if (source === 'my-dependency') {
return { id: 'my-dependency-develop', external: true };
}
return null;
}
};
}
如果 external 为 true,则绝对 id 将根据用户对 makeAbsoluteExternalsRelative 选项的选择转换为相对 id。可以通过传递 external: "relative" 来覆盖此选择,以始终将绝对 id 转换为相对 id,或者传递 external: "absolute" 来保持其为绝对 id。当返回一个对象时,相对的外部 id,即以 ./ 或 ../ 开头的 id,将 不会 被内部转换为绝对 id 并在输出中转换回相对 id,而是不变地包含在输出中。如果你希望相对 id 被重新规范化和去重,请将绝对文件系统位置作为 id 返回,并选择 external: "relative"。
如果在解析模块 id 的第一个钩子中返回 false 且没有其他模块从此模块导入任何内容,则即使该模块具有副作用,该模块也不会被包含。如果返回 true,Rollup 将使用其默认算法来包含模块中具有副作用的所有语句(例如修改全局或导出变量)。如果返回 "no-treeshake",则将关闭此模块的除屑,并且即使它为空,它也将包含在生成的块之一中。如果返回 null 或省略标志,则 moduleSideEffects 将由 treeshake.moduleSideEffects 选项确定或默认为 true。load 和 transform 钩子可以覆盖此选项。
可以在返回的对象中明确声明 resolvedBy。它将替换 this.resolve 返回的相应字段。
如果为外部模块返回 assertions 的值,则这将确定在生成 "es" 输出时如何呈现此模块的导入。例如,{id: "foo", external: true, assertions: {type: "json"}} 将导致此模块的导入显示为 import "foo" assert {type: "json"}。如果不传递值,则将使用 assertions 输入参数的值。传递一个空对象以删除任何断言。虽然 assertions 不影响产物模块的呈现,但它们仍然需要在模块的所有导入中保持一致,否则会发出警告。load 和 transform 钩子可以覆盖此选项。
有关 syntheticNamedExports 选项的影响,请参见 synthetic named exports。如果返回 null 或省略标志,则 syntheticNamedExports 将默认为 false。load 和 transform 钩子可以覆盖此选项。
有关如何使用 meta 选项,请参见 custom module meta-data。如果返回 null 或省略选项,则 meta 将默认为空对象。load 和 transform 钩子可以添加或替换此对象的属性。
请注意,虽然 resolveId 将为模块的每个导入调用一次,并且因此可以多次解析为相同的 id,但是 external、assertions、meta、moduleSideEffects 或 syntheticNamedExports 的值只能在加载模块之前设置一次。原因是在此调用之后,Rollup 将继续使用该模块的 load 和 transform 钩子,这些钩子可能会覆盖这些值,并且如果它们这样做,则应优先考虑。
当通过 this.resolve 从插件触发此钩子时,可以向此钩子传递自定义选项对象。虽然此对象将不被修改,但插件应遵循添加具有对象的 custom 属性的约定,其中键对应于选项所针对的插件的名称。有关详细信息,请参见 custom resolver options。
在观察模式下或者明确使用缓存时,缓存模块的已解析导入也会从缓存中获取,而不是再次通过 resolveId 钩子进行确定。为了防止这种情况,你可以在 shouldTransformCachedModule 钩子中为该模块返回 true。这将从缓存中删除该模块及其导入解析,并再次调用 transform 和 resolveId。