diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 77b27feceddaec..1f607cde95141c 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -154,7 +154,10 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { if (id.endsWith('.html')) { const publicPath = `/${slash(path.relative(config.root, id))}` // pre-transform - html = await applyHtmlTransforms(html, publicPath, id, preHooks) + html = await applyHtmlTransforms(html, preHooks, { + path: publicPath, + filename: id + }) let js = '' const s = new MagicString(html) @@ -381,15 +384,12 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { } const shortEmitName = path.posix.relative(config.root, id) - result = await applyHtmlTransforms( - result, - '/' + shortEmitName, - id, - postHooks, - undefined, + result = await applyHtmlTransforms(result, postHooks, { + path: '/' + shortEmitName, + filename: id, bundle, chunk - ) + }) this.emitFile({ type: 'asset', @@ -431,6 +431,7 @@ export interface IndexHtmlTransformContext { server?: ViteDevServer bundle?: OutputBundle chunk?: OutputChunk + originalUrl?: string } export type IndexHtmlTransformHook = ( @@ -469,26 +470,14 @@ export function resolveHtmlTransforms( export async function applyHtmlTransforms( html: string, - path: string, - filename: string, hooks: IndexHtmlTransformHook[], - server?: ViteDevServer, - bundle?: OutputBundle, - chunk?: OutputChunk + ctx: IndexHtmlTransformContext ): Promise { const headTags: HtmlTagDescriptor[] = [] const headPrependTags: HtmlTagDescriptor[] = [] const bodyTags: HtmlTagDescriptor[] = [] const bodyPrependTags: HtmlTagDescriptor[] = [] - const ctx: IndexHtmlTransformContext = { - path, - filename, - server, - bundle, - chunk - } - for (const hook of hooks) { const res = await hook(html, ctx) if (!res) { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 2dfbad39541535..2c90dd6d08906f 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -196,7 +196,11 @@ export interface ViteDevServer { /** * Apply vite built-in HTML transforms and any plugin HTML transforms. */ - transformIndexHtml(url: string, html: string): Promise + transformIndexHtml( + url: string, + html: string, + originalUrl?: string + ): Promise /** * Util for transforming a file with esbuild. * Can be useful for certain plugins. diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index 200a951bcabe80..2e988db819a2d8 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -1,7 +1,7 @@ import fs from 'fs' import path from 'path' import MagicString from 'magic-string' -import { NodeTypes } from '@vue/compiler-dom' +import { AttributeNode, NodeTypes } from '@vue/compiler-dom' import { Connect } from 'types/connect' import { applyHtmlTransforms, @@ -10,7 +10,7 @@ import { resolveHtmlTransforms, traverseHtml } from '../../plugins/html' -import { ViteDevServer } from '../..' +import { ResolvedConfig, ViteDevServer } from '../..' import { send } from '../send' import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants' import { cleanUrl, fsPathFromId } from '../../utils' @@ -18,17 +18,16 @@ import { assetAttrsConfig } from '../../plugins/html' export function createDevHtmlTransformFn( server: ViteDevServer -): (url: string, html: string) => Promise { +): (url: string, html: string, originalUrl: string) => Promise { const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins) - return (url: string, html: string): Promise => { - return applyHtmlTransforms( - html, - url, - getHtmlFilename(url, server), - [...preHooks, devHtmlHook, ...postHooks], - server - ) + return (url: string, html: string, originalUrl: string): Promise => { + return applyHtmlTransforms(html, [...preHooks, devHtmlHook, ...postHooks], { + path: url, + filename: getHtmlFilename(url, server), + server, + originalUrl + }) } } @@ -41,9 +40,44 @@ function getHtmlFilename(url: string, server: ViteDevServer) { } const startsWithSingleSlashRE = /^\/(?!\/)/ +const processNodeUrl = ( + node: AttributeNode, + s: MagicString, + config: ResolvedConfig, + htmlPath: string, + originalUrl?: string +) => { + const url = node.value?.content || '' + if (startsWithSingleSlashRE.test(url)) { + // prefix with base + s.overwrite( + node.value!.loc.start.offset, + node.value!.loc.end.offset, + `"${config.base + url.slice(1)}"` + ) + } else if ( + url.startsWith('.') && + originalUrl && + originalUrl !== '/' && + htmlPath === '/index.html' + ) { + // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets + // path will add `/a/` prefix, it will caused 404. + // rewrite before `./index.js` -> `localhost:3000/a/index.js`. + // rewrite after `../index.js` -> `localhost:3000/index.js`. + s.overwrite( + node.value!.loc.start.offset, + node.value!.loc.end.offset, + `"${path.posix.join( + path.posix.relative(originalUrl, '/'), + url.slice(1) + )}"` + ) + } +} const devHtmlHook: IndexHtmlTransformHook = async ( html, - { path: htmlPath, server } + { path: htmlPath, server, originalUrl } ) => { // TODO: solve this design issue // Optional chain expressions can return undefined by design @@ -67,15 +101,7 @@ const devHtmlHook: IndexHtmlTransformHook = async ( } if (src) { - const url = src.value?.content || '' - if (startsWithSingleSlashRE.test(url)) { - // prefix with base - s.overwrite( - src.value!.loc.start.offset, - src.value!.loc.end.offset, - `"${config.base + url.slice(1)}"` - ) - } + processNodeUrl(src, s, config, htmlPath, originalUrl) } else if (isModule) { // inline js module. convert to src="proxy" s.overwrite( @@ -97,14 +123,7 @@ const devHtmlHook: IndexHtmlTransformHook = async ( p.value && assetAttrs.includes(p.name) ) { - const url = p.value.content || '' - if (startsWithSingleSlashRE.test(url)) { - s.overwrite( - p.value.loc.start.offset, - p.value.loc.end.offset, - `"${config.base + url.slice(1)}"` - ) - } + processNodeUrl(p, s, config, htmlPath, originalUrl) } } } @@ -138,7 +157,7 @@ export function indexHtmlMiddleware( if (fs.existsSync(filename)) { try { let html = fs.readFileSync(filename, 'utf-8') - html = await server.transformIndexHtml(url, html) + html = await server.transformIndexHtml(url, html, req.originalUrl) return send(req, res, html, 'html') } catch (e) { return next(e)