fix(build): resolve synthesized not-found entries on windows

The bundler hands entries over as native paths, which never matched the
posix `srcDir`, so a synthesized `<locale>/404.md` was left unresolved on
windows. Ids are normalized before matching, with drive letters compared
case-insensitively. The plugin's unit tests use an `.mjs` config like the
other node tests, which keeps vite's config loader quiet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
feat/not-found
Divyansh Singh 1 week ago
parent 840e5514e5
commit efd64d8a7b

@ -12,7 +12,7 @@ async function site(files: Record<string, string>, config = '') {
const root = mkdtempSync(join(tmpdir(), 'vp-not-found-'))
mkdirSync(join(root, '.vitepress'), { recursive: true })
writeFileSync(
join(root, '.vitepress/config.ts'),
join(root, '.vitepress/config.mjs'),
`export default { ${locales}, ${config} }`
)
for (const [file, content] of Object.entries(files)) {
@ -22,8 +22,8 @@ async function site(files: Record<string, string>, config = '') {
const siteConfig = await resolveConfig(root, 'build', 'production')
const plugin = notFoundPlugin(siteConfig)
const hooks = {
resolveId: (id: string) =>
(plugin.resolveId as any).handler.call(undefined, id, undefined, {}),
resolveId: (id: string, importer?: string) =>
(plugin.resolveId as any).handler.call(undefined, id, importer, {}),
load: (id: string) => (plugin.load as any).handler.call(undefined, id)
}
const file = (page: string) => normalizePath(join(siteConfig.srcDir, page))
@ -45,6 +45,13 @@ describe('node/plugins/notFoundPlugin', () => {
])
expect(s.resolveId('/zh/404.md')).toBe(s.file('zh/404.md'))
expect(s.resolveId('/zh/404.md?t=123')).toBe(s.file('zh/404.md'))
// the bundler hands entries over as native paths
expect(s.resolveId(join(s.siteConfig.srcDir, 'zh', '404.md'))).toBe(
s.file('zh/404.md')
)
expect(s.resolveId('./zh/404.md', s.file('index.md'))).toBe(
s.file('zh/404.md')
)
expect(s.load(s.file('404.md'))).toContain('<NotFound />')
expect(s.load(s.file('zh/404.md'))).toContain('<NotFound />')
} finally {

@ -42,6 +42,13 @@ export const notFoundPlugin = (siteConfig: SiteConfig): Plugin => {
return index === -1 ? [id, ''] : [id.slice(0, index), id.slice(index + 1)]
}
// ids arrive as urls, posix paths or native windows paths (the bundler
// entries), with the drive letter in either case
const isUnderSrcDir = (file: string) =>
file
.replace(/^[a-z]:/i, (d) => d.toLowerCase())
.startsWith(srcDir.replace(/^[a-z]:/i, (d) => d.toLowerCase()))
// the synthesized page a would-be file stands for, and the authored root
// page it inherits when there is one
const virtualPage = (file: string) => {
@ -64,15 +71,17 @@ export const notFoundPlugin = (siteConfig: SiteConfig): Plugin => {
resolveId: {
filter: { id: notFoundRE },
handler(id, importer) {
const [file, query] = splitQuery(id)
const [rawFile, query] = splitQuery(id)
// sub-requests (`?vue&type=…`) belong to the module that owns them
if (query && !/^t=\d+$/.test(query)) return
const resolved = file.startsWith(srcDir)
// normalizing first would fold `./x` into `x`, so test the raw id
const file = normalizePath(rawFile)
const resolved = isUnderSrcDir(file)
? file
: file.startsWith('/')
? normalizePath(path.join(srcDir, file))
: importer && file.startsWith('.')
? normalizePath(path.resolve(path.dirname(importer), file))
: rawFile.startsWith('/')
? normalizePath(path.join(srcDir, rawFile))
: importer && rawFile.startsWith('.')
? normalizePath(path.resolve(path.dirname(importer), rawFile))
: undefined
const page = resolved && virtualPage(resolved)
if (page) return page.inherits ? VIRTUAL_PREFIX + resolved : resolved

Loading…
Cancel
Save