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)