Skip to content

Commit 9f10261

Browse files
authored
fix: replace runner-side path normalization with fetchModule-side resolve (#18361)
1 parent 8bfe247 commit 9f10261

File tree

11 files changed

+67
-68
lines changed

11 files changed

+67
-68
lines changed

docs/guide/api-environment-runtimes.md

+1-8
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,10 @@ Module runner exposes `import` method. When Vite server triggers `full-reload` H
150150

151151
```js
152152
import { ModuleRunner, ESModulesEvaluator } from 'vite/module-runner'
153-
import { root, transport } from './rpc-implementation.js'
153+
import { transport } from './rpc-implementation.js'
154154

155155
const moduleRunner = new ModuleRunner(
156156
{
157-
root,
158157
transport,
159158
},
160159
new ESModulesEvaluator(),
@@ -180,10 +179,6 @@ type ModuleRunnerTransport = unknown
180179

181180
// ---cut---
182181
interface ModuleRunnerOptions {
183-
/**
184-
* Root of the project
185-
*/
186-
root: string
187182
/**
188183
* A set of methods to communicate with the server.
189184
*/
@@ -294,7 +289,6 @@ const transport = {
294289

295290
const runner = new ModuleRunner(
296291
{
297-
root: fileURLToPath(new URL('./', import.meta.url)),
298292
transport,
299293
},
300294
new ESModulesEvaluator(),
@@ -362,7 +356,6 @@ import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
362356

363357
export const runner = new ModuleRunner(
364358
{
365-
root: fileURLToPath(new URL('./', import.meta.url)),
366359
transport: {
367360
async invoke(data) {
368361
const response = await fetch(`http://my-vite-server/invoke`, {

packages/vite/src/module-runner/runner.ts

-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import type {
1717
SSRImportMetadata,
1818
} from './types'
1919
import {
20-
normalizeAbsoluteUrl,
2120
posixDirname,
2221
posixPathToFileHref,
2322
posixResolve,
@@ -52,7 +51,6 @@ export class ModuleRunner {
5251
})
5352
private readonly transport: NormalizedModuleRunnerTransport
5453
private readonly resetSourceMapSupport?: () => void
55-
private readonly root: string
5654
private readonly concurrentModuleNodePromises = new Map<
5755
string,
5856
Promise<EvaluatedModuleNode>
@@ -65,8 +63,6 @@ export class ModuleRunner {
6563
public evaluator: ModuleEvaluator = new ESModulesEvaluator(),
6664
private debug?: ModuleRunnerDebugger,
6765
) {
68-
const root = this.options.root
69-
this.root = root[root.length - 1] === '/' ? root : `${root}/`
7066
this.evaluatedModules = options.evaluatedModules ?? new EvaluatedModules()
7167
this.transport = normalizeModuleRunnerTransport(options.transport)
7268
if (options.hmr !== false) {
@@ -237,8 +233,6 @@ export class ModuleRunner {
237233
url: string,
238234
importer?: string,
239235
): Promise<EvaluatedModuleNode> {
240-
url = normalizeAbsoluteUrl(url, this.root)
241-
242236
let cached = this.concurrentModuleNodePromises.get(url)
243237
if (!cached) {
244238
const cachedModule = this.evaluatedModules.getModuleByUrl(url)

packages/vite/src/module-runner/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ export interface ModuleRunnerHmr {
8585
export interface ModuleRunnerOptions {
8686
/**
8787
* Root of the project
88+
* @deprecated not used and to be removed
8889
*/
89-
root: string
90+
root?: string
9091
/**
9192
* A set of methods to communicate with the server.
9293
*/

packages/vite/src/module-runner/utils.ts

+1-22
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,5 @@
11
import * as pathe from 'pathe'
2-
import { isWindows, slash } from '../shared/utils'
3-
4-
export function normalizeAbsoluteUrl(url: string, root: string): string {
5-
url = slash(url)
6-
7-
// file:///C:/root/id.js -> C:/root/id.js
8-
if (url.startsWith('file://')) {
9-
// 8 is the length of "file:///"
10-
url = decodeURI(url.slice(isWindows ? 8 : 7))
11-
}
12-
13-
// strip root from the URL because fetchModule prefers a public served url path
14-
// packages/vite/src/node/server/moduleGraph.ts:17
15-
if (url.startsWith(root)) {
16-
// /root/id.js -> /id.js
17-
// C:/root/id.js -> /id.js
18-
// 1 is to keep the leading slash
19-
url = url.slice(root.length - 1)
20-
}
21-
22-
return url
23-
}
2+
import { isWindows } from '../shared/utils'
243

254
export const decodeBase64 =
265
typeof atob !== 'undefined'

packages/vite/src/node/ssr/fetchModule.ts

+8-16
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ export async function fetchModule(
3333
return { externalize: url, type: 'builtin' }
3434
}
3535

36-
if (isExternalUrl(url)) {
36+
// handle file urls from not statically analyzable dynamic import
37+
const isFileUrl = url.startsWith('file://')
38+
39+
if (isExternalUrl(url) && !isFileUrl) {
3740
return { externalize: url, type: 'network' }
3841
}
3942

4043
// if there is no importer, the file is an entry point
4144
// entry points are always internalized
42-
if (importer && url[0] !== '.' && url[0] !== '/') {
45+
if (!isFileUrl && importer && url[0] !== '.' && url[0] !== '/') {
4346
const { isProduction, root } = environment.config
4447
const { externalConditions, dedupe, preserveSymlinks } =
4548
environment.config.resolve
@@ -74,7 +77,7 @@ export async function fetchModule(
7477

7578
// this is an entry point module, very high chance it's not resolved yet
7679
// for example: runner.import('./some-file') or runner.import('/some-file')
77-
if (!importer) {
80+
if (isFileUrl || !importer) {
7881
const resolved = await environment.pluginContainer.resolveId(url)
7982
if (!resolved) {
8083
throw new Error(`[vite] cannot find entry point module '${url}'.`)
@@ -84,8 +87,8 @@ export async function fetchModule(
8487

8588
url = unwrapId(url)
8689

87-
let mod = await environment.moduleGraph.getModuleByUrl(url)
88-
const cached = !!mod?.transformResult
90+
const mod = await environment.moduleGraph.ensureEntryFromUrl(url)
91+
const cached = !!mod.transformResult
8992

9093
// if url is already cached, we can just confirm it's also cached on the server
9194
if (options.cached && cached) {
@@ -102,17 +105,6 @@ export async function fetchModule(
102105
)
103106
}
104107

105-
// module entry should be created by transformRequest
106-
mod ??= await environment.moduleGraph.getModuleByUrl(url)
107-
108-
if (!mod) {
109-
throw new Error(
110-
`[vite] cannot find module '${url}' ${
111-
importer ? ` imported from '${importer}'` : ''
112-
}.`,
113-
)
114-
}
115-
116108
if (options.inlineSourceMap !== false) {
117109
result = inlineSourceMap(mod, result, options.startOffset)
118110
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import path from 'node:path'
22
import * as staticModule from './simple'
3+
import { pathToFileURL } from 'node:url'
34

45
export const initialize = async () => {
56
const nameRelative = './simple'
67
const nameAbsolute = '/fixtures/simple'
78
const nameAbsoluteExtension = '/fixtures/simple.js'
9+
const absolutePath = path.join(import.meta.dirname, "simple.js")
10+
const fileUrl = pathToFileURL(absolutePath)
811
return {
912
dynamicProcessed: await import('./simple'),
1013
dynamicRelative: await import(nameRelative),
1114
dynamicAbsolute: await import(nameAbsolute),
1215
dynamicAbsoluteExtension: await import(nameAbsoluteExtension),
13-
dynamicAbsoluteFull: await import(path.join(import.meta.dirname, "simple.js")),
16+
dynamicAbsoluteFull: await import((process.platform === 'win32' ? '/@fs/' : '') + absolutePath),
17+
dynamicFileUrl: await import(fileUrl),
1418
static: staticModule,
1519
}
1620
}

packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.invoke.mjs

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// @ts-check
22

33
import { BroadcastChannel, parentPort } from 'node:worker_threads'
4-
import { fileURLToPath } from 'node:url'
54
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
65
import { createBirpc } from 'birpc'
76

@@ -20,7 +19,6 @@ const rpc = createBirpc({}, {
2019

2120
const runner = new ModuleRunner(
2221
{
23-
root: fileURLToPath(new URL('./', import.meta.url)),
2422
transport: {
2523
invoke(data) { return rpc.invoke(data) }
2624
},

packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.mjs

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// @ts-check
22

33
import { BroadcastChannel, parentPort } from 'node:worker_threads'
4-
import { fileURLToPath } from 'node:url'
54
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
65

76
if (!parentPort) {
@@ -24,7 +23,6 @@ const messagePortTransport = {
2423

2524
const runner = new ModuleRunner(
2625
{
27-
root: fileURLToPath(new URL('./', import.meta.url)),
2826
transport: messagePortTransport,
2927
},
3028
new ESModulesEvaluator(),

packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts

+39
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ describe('module runner initialization', async () => {
178178
expect(modules.static).toBe(modules.dynamicAbsolute)
179179
expect(modules.static).toBe(modules.dynamicAbsoluteExtension)
180180
expect(modules.static).toBe(modules.dynamicAbsoluteFull)
181+
expect(modules.static).toBe(modules.dynamicFileUrl)
181182
})
182183

183184
it('correctly imports a virtual module', async ({ runner }) => {
@@ -263,3 +264,41 @@ describe('optimize-deps', async () => {
263264
expect(mod.default.hello()).toMatchInlineSnapshot(`"world"`)
264265
})
265266
})
267+
268+
describe('resolveId absolute path entry', async () => {
269+
const it = await createModuleRunnerTester({
270+
plugins: [
271+
{
272+
name: 'test-resolevId',
273+
enforce: 'pre',
274+
resolveId(source) {
275+
if (
276+
source ===
277+
posix.join(this.environment.config.root, 'fixtures/basic.js')
278+
) {
279+
return '\0virtual:basic'
280+
}
281+
},
282+
load(id) {
283+
if (id === '\0virtual:basic') {
284+
return `export const name = "virtual:basic"`
285+
}
286+
},
287+
},
288+
],
289+
})
290+
291+
it('ssrLoadModule', async ({ server }) => {
292+
const mod = await server.ssrLoadModule(
293+
posix.join(server.config.root, 'fixtures/basic.js'),
294+
)
295+
expect(mod.name).toMatchInlineSnapshot(`"virtual:basic"`)
296+
})
297+
298+
it('runner', async ({ server, runner }) => {
299+
const mod = await runner.import(
300+
posix.join(server.config.root, 'fixtures/basic.js'),
301+
)
302+
expect(mod.name).toMatchInlineSnapshot(`"virtual:basic"`)
303+
})
304+
})

packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect } from 'vitest'
2-
import type { ModuleRunner } from 'vite/module-runner'
2+
import type { ViteDevServer } from '../../..'
33
import { createModuleRunnerTester, editFile, resolvePath } from './utils'
44

55
describe('module runner initialization', async () => {
@@ -18,13 +18,13 @@ describe('module runner initialization', async () => {
1818
return err
1919
}
2020
}
21-
const serializeStack = (runner: ModuleRunner, err: Error) => {
22-
return err.stack!.split('\n')[1].replace(runner.options.root, '<root>')
21+
const serializeStack = (server: ViteDevServer, err: Error) => {
22+
return err.stack!.split('\n')[1].replace(server.config.root, '<root>')
2323
}
24-
const serializeStackDeep = (runtime: ModuleRunner, err: Error) => {
24+
const serializeStackDeep = (server: ViteDevServer, err: Error) => {
2525
return err
2626
.stack!.split('\n')
27-
.map((s) => s.replace(runtime.options.root, '<root>'))
27+
.map((s) => s.replace(server.config.root, '<root>'))
2828
}
2929

3030
it('source maps are correctly applied to stack traces', async ({
@@ -35,15 +35,15 @@ describe('module runner initialization', async () => {
3535
const topLevelError = await getError(() =>
3636
runner.import('/fixtures/has-error.js'),
3737
)
38-
expect(serializeStack(runner, topLevelError)).toBe(
38+
expect(serializeStack(server, topLevelError)).toBe(
3939
' at <root>/fixtures/has-error.js:2:7',
4040
)
4141

4242
const methodError = await getError(async () => {
4343
const mod = await runner.import('/fixtures/throws-error-method.ts')
4444
mod.throwError()
4545
})
46-
expect(serializeStack(runner, methodError)).toBe(
46+
expect(serializeStack(server, methodError)).toBe(
4747
' at Module.throwError (<root>/fixtures/throws-error-method.ts:6:9)',
4848
)
4949

@@ -60,17 +60,17 @@ describe('module runner initialization', async () => {
6060
mod.throwError()
6161
})
6262

63-
expect(serializeStack(runner, methodErrorNew)).toBe(
63+
expect(serializeStack(server, methodErrorNew)).toBe(
6464
' at Module.throwError (<root>/fixtures/throws-error-method.ts:11:9)',
6565
)
6666
})
6767

68-
it('deep stacktrace', async ({ runner }) => {
68+
it('deep stacktrace', async ({ runner, server }) => {
6969
const methodError = await getError(async () => {
7070
const mod = await runner.import('/fixtures/has-error-deep.ts')
7171
mod.main()
7272
})
73-
expect(serializeStackDeep(runner, methodError).slice(0, 3)).toEqual([
73+
expect(serializeStackDeep(server, methodError).slice(0, 3)).toEqual([
7474
'Error: crash',
7575
' at crash (<root>/fixtures/has-error-deep.ts:2:9)',
7676
' at Module.main (<root>/fixtures/has-error-deep.ts:6:3)',

packages/vite/src/node/ssr/runtime/__tests__/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export async function createModuleRunnerTester(
7171
}
7272
},
7373
},
74+
...(config.plugins ?? []),
7475
],
7576
...config,
7677
})

0 commit comments

Comments
 (0)