diff --git a/__tests__/unit/node/plugins/notFoundPlugin.test.ts b/__tests__/unit/node/plugins/notFoundPlugin.test.ts index fd1639dc..af93b91f 100644 --- a/__tests__/unit/node/plugins/notFoundPlugin.test.ts +++ b/__tests__/unit/node/plugins/notFoundPlugin.test.ts @@ -12,7 +12,7 @@ async function site(files: Record, 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, 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('') expect(s.load(s.file('zh/404.md'))).toContain('') } finally { diff --git a/src/node/plugins/notFoundPlugin.ts b/src/node/plugins/notFoundPlugin.ts index 47d8c0fa..67549652 100644 --- a/src/node/plugins/notFoundPlugin.ts +++ b/src/node/plugins/notFoundPlugin.ts @@ -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