diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index d7385026c0d161..51619243caf5ec 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -6,6 +6,7 @@ import { type NormalizedModuleRunnerTransport, normalizeModuleRunnerTransport, } from '../shared/moduleRunnerTransport' +import { createIsBuiltin } from '../shared/builtin' import type { EvaluatedModuleNode } from './evaluatedModules' import { EvaluatedModules } from './evaluatedModules' import type { @@ -44,6 +45,8 @@ export class ModuleRunner { string, Promise >() + private isBuiltin?: (id: string) => boolean + private builtinsPromise?: Promise private closed = false @@ -238,6 +241,23 @@ export class ModuleRunner { return cached } + private ensureBuiltins(): Promise | undefined { + if (this.isBuiltin) return + + this.builtinsPromise ??= (async () => { + try { + this.debug?.('[module runner] fetching builtins from server') + const builtins = await this.transport.invoke('getBuiltins', []) + this.isBuiltin = createIsBuiltin(builtins) + this.debug?.('[module runner] builtins loaded:', builtins) + } finally { + this.builtinsPromise = undefined + } + })() + + return this.builtinsPromise + } + private async getModuleInformation( url: string, importer: string | undefined, @@ -247,13 +267,15 @@ export class ModuleRunner { throw new Error(`Vite module runner has been closed.`) } + await this.ensureBuiltins() + this.debug?.('[module runner] fetching', url) const isCached = !!(typeof cachedModule === 'object' && cachedModule.meta) const fetchedModule = // fast return for established externalized pattern ( - url.startsWith('data:') + url.startsWith('data:') || this.isBuiltin?.(url) ? { externalize: url, type: 'builtin' } : await this.transport.invoke('fetchModule', [ url, diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index 522f77d234ebbd..9f4b25c09531c0 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -133,6 +133,9 @@ export class DevEnvironment extends BaseEnvironment { fetchModule: (id, importer, options) => { return this.fetchModule(id, importer, options) }, + getBuiltins: async () => { + return this.config.resolve.builtins + }, }) this.hot.on( diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/builtin-import.ts b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/builtin-import.ts new file mode 100644 index 00000000000000..b69715a2a29383 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/builtin-import.ts @@ -0,0 +1,3 @@ +import { basename } from 'node:path' + +export default basename('/foo/bar/baz.txt') diff --git a/packages/vite/src/node/ssr/runtime/__tests__/server-worker-runner.invoke.spec.ts b/packages/vite/src/node/ssr/runtime/__tests__/server-worker-runner.invoke.spec.ts index 4d4c3ff7a7cf3c..33a9ed9130f5a1 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/server-worker-runner.invoke.spec.ts +++ b/packages/vite/src/node/ssr/runtime/__tests__/server-worker-runner.invoke.spec.ts @@ -98,4 +98,13 @@ describe('running module runner inside a worker and using the ModuleRunnerTransp expect(output).not.toHaveProperty('result') expect(output.error).toContain('Error: Unknown invoke error') }) + + it('resolves builtin module without server round-trip', async () => { + handleInvoke = (data: any) => server.environments.ssr.hot.handleInvoke(data) + + const output = await run('./fixtures/builtin-import.ts') + expect(output).toHaveProperty('result') + expect(output.result).toBe('baz.txt') + expect(output.error).toBeUndefined() + }) }) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index fb075bca9b1b0b..68c1883fdcca56 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -30,6 +30,7 @@ import { withTrailingSlash, } from '../shared/utils' import { VALID_ID_PREFIX } from '../shared/constants' +import { createIsBuiltin } from '../shared/builtin' import { CLIENT_ENTRY, CLIENT_PUBLIC_PATH, @@ -117,20 +118,6 @@ export function isBuiltin(builtins: (string | RegExp)[], id: string): boolean { return isBuiltin(id) } -export function createIsBuiltin( - builtins: (string | RegExp)[], -): (id: string) => boolean { - const plainBuiltinsSet = new Set( - builtins.filter((builtin) => typeof builtin === 'string'), - ) - const regexBuiltins = builtins.filter( - (builtin) => typeof builtin !== 'string', - ) - - return (id) => - plainBuiltinsSet.has(id) || regexBuiltins.some((regexp) => regexp.test(id)) -} - export const nodeLikeBuiltins: (string | RegExp)[] = [ ...nodeBuiltins, new RegExp(`^${NODE_BUILTIN_NAMESPACE}`), diff --git a/packages/vite/src/shared/builtin.ts b/packages/vite/src/shared/builtin.ts new file mode 100644 index 00000000000000..70e637e85230f5 --- /dev/null +++ b/packages/vite/src/shared/builtin.ts @@ -0,0 +1,13 @@ +export function createIsBuiltin( + builtins: (string | RegExp)[], +): (id: string) => boolean { + const plainBuiltinsSet = new Set( + builtins.filter((builtin) => typeof builtin === 'string'), + ) + const regexBuiltins = builtins.filter( + (builtin) => typeof builtin !== 'string', + ) + + return (id: string) => + plainBuiltinsSet.has(id) || regexBuiltins.some((regexp) => regexp.test(id)) +} diff --git a/packages/vite/src/shared/invokeMethods.ts b/packages/vite/src/shared/invokeMethods.ts index 7a6f3f02ef8e04..ffa45b7be6f773 100644 --- a/packages/vite/src/shared/invokeMethods.ts +++ b/packages/vite/src/shared/invokeMethods.ts @@ -82,4 +82,6 @@ export type InvokeMethods = { importer?: string, options?: FetchFunctionOptions, ) => Promise + + getBuiltins: () => Promise<(string | RegExp)[]> }