diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index 012c32e0fa6dfb..0bbd517e9728fb 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -114,6 +114,16 @@ export interface Plugin extends RollupPlugin { ctx: HmrContext ): Array | void | Promise | void> + /** + * Resolve an asset's public URL. This hook can call `this.emitFile` and + * return `__VITE_ASSET__${emitFileResult}__` to be replaced by Vite + * later in the build. + */ + resolveBuiltUrl?( + this: PluginContext, + url: string + ): Promise | string | null | undefined + /** * extend hooks with ssr flag */ diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 950f7e86c59696..a6b9a6f1bf1f41 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -128,15 +128,16 @@ export function checkPublicFile( } } -export function fileToUrl( +export async function fileToUrl( id: string, config: ResolvedConfig, ctx: PluginContext -): string | Promise { +): Promise { if (config.command === 'serve') { return fileToDevUrl(id, config) } else { - return fileToBuiltUrl(id, config, ctx) + const builtUrl = await resolveBuiltUrl(id, config, ctx) + return builtUrl || fileToBuiltUrl(id, config, ctx) } } @@ -252,19 +253,51 @@ export async function urlToBuiltUrl( url: string, importer: string, config: ResolvedConfig, - pluginContext: PluginContext + ctx: PluginContext ): Promise { + const builtUrl = await resolveBuiltUrl(url, config, ctx) + if (builtUrl) { + return builtUrl + } + if (checkPublicFile(url, config)) { return config.base + url.slice(1) } + const file = url.startsWith('/') ? path.join(config.root, url) : path.join(path.dirname(importer), url) + return fileToBuiltUrl( file, config, - pluginContext, + ctx, // skip public check since we just did it above true ) } + +async function resolveBuiltUrl( + url: string, + config: ResolvedConfig, + ctx: PluginContext +): Promise { + for (const { resolveBuiltUrl } of config.plugins) { + if (resolveBuiltUrl) { + const builtUrl = await resolveBuiltUrl.call(ctx, url) + if (builtUrl) { + const match = builtUrl.match(/^__VITE_ASSET__(.+?)__/) + if (match) { + let map = assetHashToFilenameMap.get(config) + if (!map) { + map = new Map() + assetHashToFilenameMap.set(config, map) + } + const contentHash = match[1] + map.set(contentHash, ctx.getFileName(contentHash)) + } + return builtUrl + } + } + } +}