diff --git a/packages/vite/src/node/__tests__/fixtures/file-url/entry.js b/packages/vite/src/node/__tests__/fixtures/file-url/entry.js new file mode 100644 index 00000000000000..60c71f346d9a3e --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/file-url/entry.js @@ -0,0 +1 @@ +export default 'ok' diff --git a/packages/vite/src/node/__tests__/fixtures/file-url/package.json b/packages/vite/src/node/__tests__/fixtures/file-url/package.json new file mode 100644 index 00000000000000..3dbc1ca591c055 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/file-url/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/vite/src/node/__tests__/resolve.spec.ts b/packages/vite/src/node/__tests__/resolve.spec.ts index 9929b416df744d..111cdb99a6b1a0 100644 --- a/packages/vite/src/node/__tests__/resolve.spec.ts +++ b/packages/vite/src/node/__tests__/resolve.spec.ts @@ -1,6 +1,9 @@ +import { join } from 'node:path' import { describe, expect, onTestFinished, test } from 'vitest' import { createServer } from '../server' import { createServerModuleRunner } from '../ssr/runtime/serverModuleRunner' +import type { InlineConfig } from '../config' +import { build } from '../build' describe('import and resolveId', () => { async function createTestServer() { @@ -50,3 +53,98 @@ describe('import and resolveId', () => { ]) }) }) + +describe('file url', () => { + const fileUrl = new URL('./fixtures/file-url/entry.js', import.meta.url) + + function getConfig(): InlineConfig { + return { + configFile: false, + root: join(import.meta.dirname, 'fixtures/file-url'), + logLevel: 'error', + server: { + middlewareMode: true, + }, + plugins: [ + { + name: 'virtual-file-url', + resolveId(source) { + if (source.startsWith('virtual:test-dep/')) { + return '\0' + source + } + }, + load(id) { + if (id === '\0virtual:test-dep/static') { + return ` + import * as dep from ${JSON.stringify(fileUrl.href)}; + export default dep; + ` + } + if (id === '\0virtual:test-dep/non-static') { + return ` + const dep = await import(/* @vite-ignore */ String(${JSON.stringify(fileUrl.href)})); + export default dep; + ` + } + }, + }, + ], + } + } + + test('dev', async () => { + const server = await createServer(getConfig()) + onTestFinished(() => server.close()) + + const runner = createServerModuleRunner(server.environments.ssr, { + hmr: { + logger: false, + }, + sourcemapInterceptor: false, + }) + + const mod = await runner.import('/entry.js') + expect(mod.default).toEqual('ok') + + const mod2 = await runner.import(fileUrl.href) + expect(mod2).toBe(mod) + + const mod3 = await runner.import('virtual:test-dep/static') + expect(mod3.default).toBe(mod) + + const mod4 = await runner.import('virtual:test-dep/non-static') + expect(mod4.default).toBe(mod) + }) + + test('build', async () => { + await build({ + ...getConfig(), + build: { + ssr: true, + outDir: 'dist/basic', + rollupOptions: { + input: { index: fileUrl.href }, + }, + }, + }) + const mod1 = await import( + join(import.meta.dirname, 'fixtures/file-url/dist/basic/index.js') + ) + expect(mod1.default).toBe('ok') + + await build({ + ...getConfig(), + build: { + ssr: true, + outDir: 'dist/virtual', + rollupOptions: { + input: { index: 'virtual:test-dep/static' }, + }, + }, + }) + const mod2 = await import( + join(import.meta.dirname, 'fixtures/file-url/dist/virtual/index.js') + ) + expect(mod2.default.default).toBe('ok') + }) +}) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index c8eb2575678f03..42958f966824d3 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -1,5 +1,6 @@ import fs from 'node:fs' import path from 'node:path' +import { fileURLToPath } from 'node:url' import colors from 'picocolors' import type { PartialResolvedId } from 'rollup' import { exports, imports } from 'resolve.exports' @@ -364,6 +365,11 @@ export function resolvePlugin( } } + // file url as path + if (id.startsWith('file://')) { + id = fileURLToPath(id) + } + // drive relative fs paths (only windows) if (isWindows && id[0] === '/') { const basedir = importer ? path.dirname(importer) : process.cwd() diff --git a/playground/resolve/__tests__/resolve.spec.ts b/playground/resolve/__tests__/resolve.spec.ts index 24715df90a1e29..e12ddecedb5c0d 100644 --- a/playground/resolve/__tests__/resolve.spec.ts +++ b/playground/resolve/__tests__/resolve.spec.ts @@ -123,6 +123,10 @@ test('absolute path', async () => { expect(await page.textContent('.absolute')).toMatch('[success]') }) +test('file url', async () => { + expect(await page.textContent('.file-url')).toMatch('[success]') +}) + test('browser field', async () => { expect(await page.textContent('.browser')).toMatch('[success]') }) diff --git a/playground/resolve/file-url.js b/playground/resolve/file-url.js new file mode 100644 index 00000000000000..3108786390cc71 --- /dev/null +++ b/playground/resolve/file-url.js @@ -0,0 +1 @@ +export default '[success] file-url' diff --git a/playground/resolve/index.html b/playground/resolve/index.html index 5badd9bf57bb6e..8c315f74dcd2c1 100644 --- a/playground/resolve/index.html +++ b/playground/resolve/index.html @@ -122,6 +122,9 @@
fail
+fail
+fail
diff --git a/playground/resolve/vite.config.js b/playground/resolve/vite.config.js index a840ec103298d1..c7d8d6ea059746 100644 --- a/playground/resolve/vite.config.js +++ b/playground/resolve/vite.config.js @@ -22,6 +22,10 @@ const generatedContentImports = [ specifier: normalizePath(path.resolve(__dirname, './absolute.js')), elementQuery: '.absolute', }, + { + specifier: new URL('file-url.js', import.meta.url), + elementQuery: '.file-url', + }, ] export default defineConfig({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c6be9c1e86deb..4f6164e8019a1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -435,6 +435,8 @@ importers: packages/vite/src/node/__tests__/fixtures/cjs-ssr-dep: {} + packages/vite/src/node/__tests__/fixtures/file-url: {} + packages/vite/src/node/__tests__/fixtures/test-dep-conditions: {} packages/vite/src/node/__tests__/packages/module: {}