diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 874cb19804ff15..48d8483ebbceb4 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -59,6 +59,7 @@ export const assetAttrsConfig: Record = { link: ['href'], video: ['src', 'poster'], source: ['src'], + script: ['src'], img: ['src'], image: ['xlink:href', 'href'], use: ['xlink:href', 'href'] @@ -125,8 +126,7 @@ function formatParseError(e: any, id: string, html: string): Error { export function buildHtmlPlugin(config: ResolvedConfig): Plugin { const [preHooks, postHooks] = resolveHtmlTransforms(config.plugins) const processedHtml = new Map() - const isExcludedUrl = (url: string) => - isExternalUrl(url) || isDataUrl(url) || checkPublicFile(url, config.root) + const isExcludedUrl = (url: string) => isExternalUrl(url) || isDataUrl(url) return { name: 'vite:build-html', @@ -137,94 +137,78 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { // pre-transform html = await applyHtmlTransforms(html, publicPath, id, preHooks) + const assetNodes: ElementNode[] = [] + await traverseHtml(html, id, (node) => { + node.type === NodeTypes.ELEMENT && + assetAttrsConfig[node.tag] && + assetNodes.push(node) + }) + let js = '' - const s = new MagicString(html) - const assetUrls: AttributeNode[] = [] let inlineModuleIndex = -1 - await traverseHtml(html, id, (node) => { - if (node.type !== NodeTypes.ELEMENT) { - return - } + const s = new MagicString(html) + for (const node of assetNodes) { let shouldRemove = false - // script tags - if (node.tag === 'script') { - const { src, isModule } = getScriptInfo(node) - - const url = src && src.value && src.value.content - if (url && checkPublicFile(url, config.root)) { - // referencing public dir url, prefix with base - s.overwrite( - src!.value!.loc.start.offset, - src!.value!.loc.end.offset, - config.base + url.slice(1) - ) + // For asset references in index.html, also generate an import + // statement for each - this will be handled by the asset plugin + const assetAttrs = assetAttrsConfig[node.tag] + const assetRefs = node.props.filter( + (p) => p.type === NodeTypes.ATTRIBUTE && assetAttrs.includes(p.name) + ) as AttributeNode[] + + const isModule = node.tag === 'script' && getScriptInfo(node).isModule + if (isModule) { + inlineModuleIndex++ + + // + if (!assetRefs.length && node.children.length) { + js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"` + shouldRemove = true + } + } + + for (const assetRef of assetRefs) { + const url = assetRef.value?.content + if (!url || isExcludedUrl(url)) { + continue } - if (isModule) { - inlineModuleIndex++ - if (url && !isExcludedUrl(url)) { - // - js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"` + } + // CSS references + else if (node.tag === 'link' && isCSSRequest(url)) { + js += `\nimport ${JSON.stringify(url)}` shouldRemove = true } } - } - // For asset references in index.html, also generate an import - // statement for each - this will be handled by the asset plugin - const assetAttrs = assetAttrsConfig[node.tag] - if (assetAttrs) { - for (const p of node.props) { - if ( - p.type === NodeTypes.ATTRIBUTE && - p.value && - assetAttrs.includes(p.name) - ) { - const url = p.value.content - if (!isExcludedUrl(url)) { - if (node.tag === 'link' && isCSSRequest(url)) { - // CSS references, convert to import - js += `\nimport ${JSON.stringify(url)}` - shouldRemove = true - } else { - assetUrls.push(p) - } - } else if (checkPublicFile(url, config.root)) { - s.overwrite( - p.value.loc.start.offset, - p.value.loc.end.offset, - config.base + url.slice(1) - ) - } + // for each encountered asset url, rewrite original html so that it + // references the post-build location. + if (!shouldRemove) { + const builtUrl = await urlToBuiltUrl(url, id, config, this) + if (url !== builtUrl) { + const { loc } = assetRef.value! + s.overwrite( + loc.start.offset, + loc.end.offset, + JSON.stringify(builtUrl) + ) } } } + // removed tags will be injected at the end if (shouldRemove) { - // remove the script tag from the html. we are going to inject new - // ones in the end. s.remove(node.loc.start.offset, node.loc.end.offset) } - }) - - // for each encountered asset url, rewrite original html so that it - // references the post-build location. - for (const attr of assetUrls) { - const value = attr.value! - const url = await urlToBuiltUrl(value.content, id, config, this) - s.overwrite( - value.loc.start.offset, - value.loc.end.offset, - JSON.stringify(url) - ) } processedHtml.set(id, s.toString())