diff --git a/packages/nitro/src/utils/resolver.ts b/packages/nitro/src/utils/resolver.ts index f0bde304d929..0b1f311cef65 100644 --- a/packages/nitro/src/utils/resolver.ts +++ b/packages/nitro/src/utils/resolver.ts @@ -20,6 +20,9 @@ export function createResolver(base: string): Resolver { } return { - resolve: (...path) => resolve(resolvedBase, ...path), + // Nitro writes plugin paths verbatim into a generated `import "…"` statement, where the + // backslashes of a Windows path read as escape sequences (`C:\Users` becomes `C:Users`). + // Forward slashes resolve on every platform, so normalize to them. + resolve: (...path) => resolve(resolvedBase, ...path).replace(/\\/g, '/'), }; } diff --git a/packages/nitro/test/resolver.test.ts b/packages/nitro/test/resolver.test.ts new file mode 100644 index 000000000000..6db133774126 --- /dev/null +++ b/packages/nitro/test/resolver.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { createResolver } from '../src/utils/resolver'; + +describe('createResolver', () => { + it('resolves relative to the directory of a file URL', () => { + const resolver = createResolver(import.meta.url); + + expect(resolver.resolve('../src/utils/resolver.ts')).toMatch(/packages\/nitro\/src\/utils\/resolver\.ts$/); + }); + + it('never returns backslashes, so the path survives being written into an import string', () => { + const resolver = createResolver(import.meta.url); + + expect(resolver.resolve('../runtime/plugins/server')).not.toContain('\\'); + }); +});