Skip to content

Commit 13d41d8

Browse files
authored
fix(dev): rewrite importee path at html files at spa fallback (#3239)
1 parent 24178b0 commit 13d41d8

File tree

3 files changed

+64
-52
lines changed

3 files changed

+64
-52
lines changed

packages/vite/src/node/plugins/html.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
153153
if (id.endsWith('.html')) {
154154
const publicPath = `/${slash(path.relative(config.root, id))}`
155155
// pre-transform
156-
html = await applyHtmlTransforms(html, publicPath, id, preHooks)
156+
html = await applyHtmlTransforms(html, preHooks, {
157+
path: publicPath,
158+
filename: id
159+
})
157160

158161
let js = ''
159162
const s = new MagicString(html)
@@ -374,15 +377,12 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
374377
}
375378

376379
const shortEmitName = path.posix.relative(config.root, id)
377-
result = await applyHtmlTransforms(
378-
result,
379-
'/' + shortEmitName,
380-
id,
381-
postHooks,
382-
undefined,
380+
result = await applyHtmlTransforms(result, postHooks, {
381+
path: '/' + shortEmitName,
382+
filename: id,
383383
bundle,
384384
chunk
385-
)
385+
})
386386

387387
this.emitFile({
388388
type: 'asset',
@@ -424,6 +424,7 @@ export interface IndexHtmlTransformContext {
424424
server?: ViteDevServer
425425
bundle?: OutputBundle
426426
chunk?: OutputChunk
427+
originalUrl?: string
427428
}
428429

429430
export type IndexHtmlTransformHook = (
@@ -462,26 +463,14 @@ export function resolveHtmlTransforms(
462463

463464
export async function applyHtmlTransforms(
464465
html: string,
465-
path: string,
466-
filename: string,
467466
hooks: IndexHtmlTransformHook[],
468-
server?: ViteDevServer,
469-
bundle?: OutputBundle,
470-
chunk?: OutputChunk
467+
ctx: IndexHtmlTransformContext
471468
): Promise<string> {
472469
const headTags: HtmlTagDescriptor[] = []
473470
const headPrependTags: HtmlTagDescriptor[] = []
474471
const bodyTags: HtmlTagDescriptor[] = []
475472
const bodyPrependTags: HtmlTagDescriptor[] = []
476473

477-
const ctx: IndexHtmlTransformContext = {
478-
path,
479-
filename,
480-
server,
481-
bundle,
482-
chunk
483-
}
484-
485474
for (const hook of hooks) {
486475
const res = await hook(html, ctx)
487476
if (!res) {

packages/vite/src/node/server/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,11 @@ export interface ViteDevServer {
210210
/**
211211
* Apply vite built-in HTML transforms and any plugin HTML transforms.
212212
*/
213-
transformIndexHtml(url: string, html: string): Promise<string>
213+
transformIndexHtml(
214+
url: string,
215+
html: string,
216+
originalUrl?: string
217+
): Promise<string>
214218
/**
215219
* Util for transforming a file with esbuild.
216220
* Can be useful for certain plugins.

packages/vite/src/node/server/middlewares/indexHtml.ts

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'fs'
22
import path from 'path'
33
import MagicString from 'magic-string'
4-
import { NodeTypes } from '@vue/compiler-dom'
4+
import { AttributeNode, NodeTypes } from '@vue/compiler-dom'
55
import { Connect } from 'types/connect'
66
import {
77
applyHtmlTransforms,
@@ -10,25 +10,24 @@ import {
1010
resolveHtmlTransforms,
1111
traverseHtml
1212
} from '../../plugins/html'
13-
import { ViteDevServer } from '../..'
13+
import { ResolvedConfig, ViteDevServer } from '../..'
1414
import { send } from '../send'
1515
import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
1616
import { cleanUrl, fsPathFromId } from '../../utils'
1717
import { assetAttrsConfig } from '../../plugins/html'
1818

1919
export function createDevHtmlTransformFn(
2020
server: ViteDevServer
21-
): (url: string, html: string) => Promise<string> {
21+
): (url: string, html: string, originalUrl: string) => Promise<string> {
2222
const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
2323

24-
return (url: string, html: string): Promise<string> => {
25-
return applyHtmlTransforms(
26-
html,
27-
url,
28-
getHtmlFilename(url, server),
29-
[...preHooks, devHtmlHook, ...postHooks],
30-
server
31-
)
24+
return (url: string, html: string, originalUrl: string): Promise<string> => {
25+
return applyHtmlTransforms(html, [...preHooks, devHtmlHook, ...postHooks], {
26+
path: url,
27+
filename: getHtmlFilename(url, server),
28+
server,
29+
originalUrl
30+
})
3231
}
3332
}
3433

@@ -41,9 +40,44 @@ function getHtmlFilename(url: string, server: ViteDevServer) {
4140
}
4241

4342
const startsWithSingleSlashRE = /^\/(?!\/)/
43+
const processNodeUrl = (
44+
node: AttributeNode,
45+
s: MagicString,
46+
config: ResolvedConfig,
47+
htmlPath: string,
48+
originalUrl?: string
49+
) => {
50+
const url = node.value?.content || ''
51+
if (startsWithSingleSlashRE.test(url)) {
52+
// prefix with base
53+
s.overwrite(
54+
node.value!.loc.start.offset,
55+
node.value!.loc.end.offset,
56+
`"${config.base + url.slice(1)}"`
57+
)
58+
} else if (
59+
url.startsWith('.') &&
60+
originalUrl &&
61+
originalUrl !== '/' &&
62+
htmlPath === '/index.html'
63+
) {
64+
// #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets
65+
// path will add `/a/` prefix, it will caused 404.
66+
// rewrite before `./index.js` -> `localhost:3000/a/index.js`.
67+
// rewrite after `../index.js` -> `localhost:3000/index.js`.
68+
s.overwrite(
69+
node.value!.loc.start.offset,
70+
node.value!.loc.end.offset,
71+
`"${path.posix.join(
72+
path.posix.relative(originalUrl, '/'),
73+
url.slice(1)
74+
)}"`
75+
)
76+
}
77+
}
4478
const devHtmlHook: IndexHtmlTransformHook = async (
4579
html,
46-
{ path: htmlPath, server }
80+
{ path: htmlPath, server, originalUrl }
4781
) => {
4882
// TODO: solve this design issue
4983
// Optional chain expressions can return undefined by design
@@ -67,15 +101,7 @@ const devHtmlHook: IndexHtmlTransformHook = async (
67101
}
68102

69103
if (src) {
70-
const url = src.value?.content || ''
71-
if (startsWithSingleSlashRE.test(url)) {
72-
// prefix with base
73-
s.overwrite(
74-
src.value!.loc.start.offset,
75-
src.value!.loc.end.offset,
76-
`"${config.base + url.slice(1)}"`
77-
)
78-
}
104+
processNodeUrl(src, s, config, htmlPath, originalUrl)
79105
} else if (isModule) {
80106
// inline js module. convert to src="proxy"
81107
s.overwrite(
@@ -97,14 +123,7 @@ const devHtmlHook: IndexHtmlTransformHook = async (
97123
p.value &&
98124
assetAttrs.includes(p.name)
99125
) {
100-
const url = p.value.content || ''
101-
if (startsWithSingleSlashRE.test(url)) {
102-
s.overwrite(
103-
p.value.loc.start.offset,
104-
p.value.loc.end.offset,
105-
`"${config.base + url.slice(1)}"`
106-
)
107-
}
126+
processNodeUrl(p, s, config, htmlPath, originalUrl)
108127
}
109128
}
110129
}
@@ -139,7 +158,7 @@ export function indexHtmlMiddleware(
139158
if (fs.existsSync(filename)) {
140159
try {
141160
let html = fs.readFileSync(filename, 'utf-8')
142-
html = await server.transformIndexHtml(url, html)
161+
html = await server.transformIndexHtml(url, html, req.originalUrl)
143162
return send(req, res, html, 'html')
144163
} catch (e) {
145164
return next(e)

0 commit comments

Comments
 (0)