diff --git a/docs/guide/api-environment-runtimes.md b/docs/guide/api-environment-runtimes.md index a257bbd231ec77..37a5f68badc4c6 100644 --- a/docs/guide/api-environment-runtimes.md +++ b/docs/guide/api-environment-runtimes.md @@ -150,11 +150,10 @@ Module runner exposes `import` method. When Vite server triggers `full-reload` H ```js import { ModuleRunner, ESModulesEvaluator } from 'vite/module-runner' -import { root, transport } from './rpc-implementation.js' +import { transport } from './rpc-implementation.js' const moduleRunner = new ModuleRunner( { - root, transport, }, new ESModulesEvaluator(), @@ -180,10 +179,6 @@ type ModuleRunnerTransport = unknown // ---cut--- interface ModuleRunnerOptions { - /** - * Root of the project - */ - root: string /** * A set of methods to communicate with the server. */ @@ -294,7 +289,6 @@ const transport = { const runner = new ModuleRunner( { - root: fileURLToPath(new URL('./', import.meta.url)), transport, }, new ESModulesEvaluator(), @@ -362,7 +356,6 @@ import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner' export const runner = new ModuleRunner( { - root: fileURLToPath(new URL('./', import.meta.url)), transport: { async invoke(data) { const response = await fetch(`http://my-vite-server/invoke`, { diff --git a/package.json b/package.json index c043790c01ab13..34e089fd50d2c1 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "simple-git-hooks": "^2.11.1", "tslib": "^2.8.1", "tsx": "^4.19.2", - "typescript": "~5.6.2", + "typescript": "~5.7.2", "typescript-eslint": "^8.18.2", "vite": "workspace:*", "vitest": "^2.1.8" diff --git a/packages/create-vite/template-lit-ts/package.json b/packages/create-vite/template-lit-ts/package.json index 8e5616f892cf3d..19b32f3e38ffae 100644 --- a/packages/create-vite/template-lit-ts/package.json +++ b/packages/create-vite/template-lit-ts/package.json @@ -12,7 +12,7 @@ "lit": "^3.2.1" }, "devDependencies": { - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5" } } diff --git a/packages/create-vite/template-preact-ts/package.json b/packages/create-vite/template-preact-ts/package.json index 11023aa83f321a..53cdccd7869f06 100644 --- a/packages/create-vite/template-preact-ts/package.json +++ b/packages/create-vite/template-preact-ts/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@preact/preset-vite": "^2.9.3", - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5" } } diff --git a/packages/create-vite/template-qwik-ts/package.json b/packages/create-vite/template-qwik-ts/package.json index 660e1cff25d63a..d99f6e98464846 100644 --- a/packages/create-vite/template-qwik-ts/package.json +++ b/packages/create-vite/template-qwik-ts/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "serve": "^14.2.4", - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5" }, "dependencies": { diff --git a/packages/create-vite/template-react-ts/package.json b/packages/create-vite/template-react-ts/package.json index bd9e176a1680b8..ee437e9544d0dc 100644 --- a/packages/create-vite/template-react-ts/package.json +++ b/packages/create-vite/template-react-ts/package.json @@ -22,7 +22,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", - "typescript": "~5.6.2", + "typescript": "~5.7.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5" } diff --git a/packages/create-vite/template-solid-ts/package.json b/packages/create-vite/template-solid-ts/package.json index 6d9c786d9a6c0b..59d243f7170882 100644 --- a/packages/create-vite/template-solid-ts/package.json +++ b/packages/create-vite/template-solid-ts/package.json @@ -12,7 +12,7 @@ "solid-js": "^1.9.3" }, "devDependencies": { - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5", "vite-plugin-solid": "^2.11.0" } diff --git a/packages/create-vite/template-svelte-ts/package.json b/packages/create-vite/template-svelte-ts/package.json index 6a3b571459dd85..c0fb40d8c541aa 100644 --- a/packages/create-vite/template-svelte-ts/package.json +++ b/packages/create-vite/template-svelte-ts/package.json @@ -14,7 +14,7 @@ "@tsconfig/svelte": "^5.0.4", "svelte": "^5.15.0", "svelte-check": "^4.1.1", - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5" } } diff --git a/packages/create-vite/template-vanilla-ts/package.json b/packages/create-vite/template-vanilla-ts/package.json index 257d79dfc35bfb..26bc55f99219b2 100644 --- a/packages/create-vite/template-vanilla-ts/package.json +++ b/packages/create-vite/template-vanilla-ts/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "devDependencies": { - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5" } } diff --git a/packages/create-vite/template-vue-ts/package.json b/packages/create-vite/template-vue-ts/package.json index 9473a1b99ffc67..c81aae9841d16f 100644 --- a/packages/create-vite/template-vue-ts/package.json +++ b/packages/create-vite/template-vue-ts/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", "@vue/tsconfig": "^0.7.0", - "typescript": "~5.6.2", + "typescript": "~5.7.2", "vite": "^6.0.5", "vue-tsc": "^2.2.0" } diff --git a/packages/vite/package.json b/packages/vite/package.json index 590c26daa96f4b..09bf309c5f95ca 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -85,7 +85,7 @@ }, "//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!", "dependencies": { - "esbuild": "0.24.0", + "esbuild": "^0.24.2", "postcss": "^8.4.49", "rollup": "^4.23.0" }, diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index 48942fae5c9964..dcbbbe77688fb9 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -17,7 +17,6 @@ import type { SSRImportMetadata, } from './types' import { - normalizeAbsoluteUrl, posixDirname, posixPathToFileHref, posixResolve, @@ -52,7 +51,6 @@ export class ModuleRunner { }) private readonly transport: NormalizedModuleRunnerTransport private readonly resetSourceMapSupport?: () => void - private readonly root: string private readonly concurrentModuleNodePromises = new Map< string, Promise @@ -65,8 +63,6 @@ export class ModuleRunner { public evaluator: ModuleEvaluator = new ESModulesEvaluator(), private debug?: ModuleRunnerDebugger, ) { - const root = this.options.root - this.root = root[root.length - 1] === '/' ? root : `${root}/` this.evaluatedModules = options.evaluatedModules ?? new EvaluatedModules() this.transport = normalizeModuleRunnerTransport(options.transport) if (options.hmr !== false) { @@ -237,8 +233,6 @@ export class ModuleRunner { url: string, importer?: string, ): Promise { - url = normalizeAbsoluteUrl(url, this.root) - let cached = this.concurrentModuleNodePromises.get(url) if (!cached) { const cachedModule = this.evaluatedModules.getModuleByUrl(url) diff --git a/packages/vite/src/module-runner/types.ts b/packages/vite/src/module-runner/types.ts index dbd769f26f9512..e4595f19cdabea 100644 --- a/packages/vite/src/module-runner/types.ts +++ b/packages/vite/src/module-runner/types.ts @@ -85,8 +85,9 @@ export interface ModuleRunnerHmr { export interface ModuleRunnerOptions { /** * Root of the project + * @deprecated not used and to be removed */ - root: string + root?: string /** * A set of methods to communicate with the server. */ diff --git a/packages/vite/src/module-runner/utils.ts b/packages/vite/src/module-runner/utils.ts index 4dfd51d93be686..d5829e1b43272b 100644 --- a/packages/vite/src/module-runner/utils.ts +++ b/packages/vite/src/module-runner/utils.ts @@ -1,26 +1,5 @@ import * as pathe from 'pathe' -import { isWindows, slash } from '../shared/utils' - -export function normalizeAbsoluteUrl(url: string, root: string): string { - url = slash(url) - - // file:///C:/root/id.js -> C:/root/id.js - if (url.startsWith('file://')) { - // 8 is the length of "file:///" - url = decodeURI(url.slice(isWindows ? 8 : 7)) - } - - // strip root from the URL because fetchModule prefers a public served url path - // packages/vite/src/node/server/moduleGraph.ts:17 - if (url.startsWith(root)) { - // /root/id.js -> /id.js - // C:/root/id.js -> /id.js - // 1 is to keep the leading slash - url = url.slice(root.length - 1) - } - - return url -} +import { isWindows } from '../shared/utils' export const decodeBase64 = typeof atob !== 'undefined' diff --git a/packages/vite/src/node/packages.ts b/packages/vite/src/node/packages.ts index 468fc4cbb0595b..5d50f285ee3fa5 100644 --- a/packages/vite/src/node/packages.ts +++ b/packages/vite/src/node/packages.ts @@ -6,6 +6,7 @@ import { isInNodeModules, normalizePath, safeRealpathSync, + stripBomTag, tryStatSync, } from './utils' import type { Plugin } from './plugin' @@ -175,7 +176,7 @@ export function findNearestMainPackageData( } export function loadPackageData(pkgPath: string): PackageData { - const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) + const data = JSON.parse(stripBomTag(fs.readFileSync(pkgPath, 'utf-8'))) const pkgDir = normalizePath(path.dirname(pkgPath)) const { sideEffects } = data let hasSideEffects: (id: string) => boolean | null diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 9a13e981fa01c8..530173eef545cb 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -83,6 +83,7 @@ import { } from '../utils' import type { Logger } from '../logger' import { cleanUrl, isWindows, slash } from '../../shared/utils' +import { NULL_BYTE_PLACEHOLDER } from '../../shared/constants' import { createBackCompatIdResolver } from '../idResolver' import type { ResolveIdFn } from '../idResolver' import { PartialEnvironment } from '../baseEnvironment' @@ -3165,10 +3166,9 @@ async function compileLightningCSS( ): ReturnType { const { config } = environment const deps = new Set() - // Relative path is needed to get stable hash when using CSS modules - const filename = cleanUrl(path.relative(config.root, id)) - const toAbsolute = (filePath: string) => - path.isAbsolute(filePath) ? filePath : path.join(config.root, filePath) + // replace null byte as lightningcss treats that as a string terminator + // https://github.com/parcel-bundler/lightningcss/issues/874 + const filename = id.replace('\0', NULL_BYTE_PLACEHOLDER) let res: LightningCssTransformAttributeResult | LightningCssTransformResult try { @@ -3185,16 +3185,14 @@ async function compileLightningCSS( ).bundleAsync({ ...config.css.lightningcss, filename, + // projectRoot is needed to get stable hash when using CSS modules + projectRoot: config.root, resolver: { read(filePath) { if (filePath === filename) { return src } - // This happens with html-proxy (#13776) - if (!filePath.endsWith('.css')) { - return src - } - return fs.readFileSync(toAbsolute(filePath), 'utf-8') + return fs.readFileSync(filePath, 'utf-8') }, async resolve(id, from) { const publicFile = checkPublicFile( @@ -3207,7 +3205,7 @@ async function compileLightningCSS( const resolved = await getAtImportResolvers( environment.getTopLevelConfig(), - ).css(environment, id, toAbsolute(from)) + ).css(environment, id, from) if (resolved) { deps.add(resolved) @@ -3229,7 +3227,7 @@ async function compileLightningCSS( } catch (e) { e.message = `[lightningcss] ${e.message}` e.loc = { - file: toAbsolute(e.fileName), + file: e.fileName.replace(NULL_BYTE_PLACEHOLDER, '\0'), line: e.loc.line, column: e.loc.column - 1, // 1-based } @@ -3247,7 +3245,10 @@ async function compileLightningCSS( if (skipUrlReplacer(dep.url)) { replaceUrl = dep.url } else if (urlReplacer) { - replaceUrl = await urlReplacer(dep.url, toAbsolute(dep.loc.filePath)) + replaceUrl = await urlReplacer( + dep.url, + dep.loc.filePath.replace(NULL_BYTE_PLACEHOLDER, '\0'), + ) } else { replaceUrl = dep.url } diff --git a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts index e8570c6d4afedf..d7db971e56218c 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts @@ -14,10 +14,9 @@ const ssrTransformSimpleCode = async (code: string, url?: string) => test('default import', async () => { expect( await ssrTransformSimpleCode(`import foo from 'vue';console.log(foo.bar)`), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["default"]}); - console.log(__vite_ssr_import_0__.default.bar)" - `) + ).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["default"]});console.log(__vite_ssr_import_0__.default.bar)"`, + ) }) test('named import', async () => { @@ -25,10 +24,9 @@ test('named import', async () => { await ssrTransformSimpleCode( `import { ref } from 'vue';function foo() { return ref(0) }`, ), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref"]}); - function foo() { return (0,__vite_ssr_import_0__.ref)(0) }" - `) + ).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref"]});function foo() { return (0,__vite_ssr_import_0__.ref)(0) }"`, + ) }) test('named import: arbitrary module namespace specifier', async () => { @@ -36,10 +34,9 @@ test('named import: arbitrary module namespace specifier', async () => { await ssrTransformSimpleCode( `import { "some thing" as ref } from 'vue';function foo() { return ref(0) }`, ), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["some thing"]}); - function foo() { return (0,__vite_ssr_import_0__["some thing"])(0) }" - `) + ).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["some thing"]});function foo() { return (0,__vite_ssr_import_0__["some thing"])(0) }"`, + ) }) test('namespace import', async () => { @@ -47,10 +44,9 @@ test('namespace import', async () => { await ssrTransformSimpleCode( `import * as vue from 'vue';function foo() { return vue.ref(0) }`, ), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue"); - function foo() { return __vite_ssr_import_0__.ref(0) }" - `) + ).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");function foo() { return __vite_ssr_import_0__.ref(0) }"`, + ) }) test('export function declaration', async () => { @@ -93,7 +89,6 @@ test('export named from', async () => { await ssrTransformSimpleCode(`export { ref, computed as c } from 'vue'`), ).toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref","computed"]}); - Object.defineProperty(__vite_ssr_exports__, "ref", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }}); Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }});" `) @@ -106,7 +101,6 @@ test('named exports of imported binding', async () => { ), ).toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]}); - Object.defineProperty(__vite_ssr_exports__, "createApp", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.createApp }});" `) }) @@ -117,11 +111,9 @@ test('export * from', async () => { `export * from 'vue'\n` + `export * from 'react'`, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue"); - __vite_ssr_exportAll__(__vite_ssr_import_0__); + "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");__vite_ssr_exportAll__(__vite_ssr_import_0__); ; - const __vite_ssr_import_1__ = await __vite_ssr_import__("react"); - __vite_ssr_exportAll__(__vite_ssr_import_1__); + const __vite_ssr_import_1__ = await __vite_ssr_import__("react");__vite_ssr_exportAll__(__vite_ssr_import_1__); " `) }) @@ -130,7 +122,6 @@ test('export * as from', async () => { expect(await ssrTransformSimpleCode(`export * as foo from 'vue'`)) .toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue"); - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }});" `) }) @@ -140,7 +131,6 @@ test('export * as from arbitrary module namespace identifier', async () => { await ssrTransformSimpleCode(`export * as "arbitrary string" from 'vue'`), ).toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue"); - Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }});" `) }) @@ -163,7 +153,6 @@ test('export as from arbitrary module namespace identifier', async () => { ), ).toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["arbitrary string2"]}); - Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__["arbitrary string2"] }});" `) }) @@ -180,9 +169,7 @@ test('export then import minified', async () => { `export * from 'vue';import {createApp} from 'vue';`, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]}); - const __vite_ssr_import_1__ = await __vite_ssr_import__("vue"); - __vite_ssr_exportAll__(__vite_ssr_import_1__); + "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("vue");__vite_ssr_exportAll__(__vite_ssr_import_1__); " `) }) @@ -192,9 +179,94 @@ test('hoist import to top', async () => { await ssrTransformSimpleCode( `path.resolve('server.js');import path from 'node:path';`, ), + ).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["default"]});__vite_ssr_import_0__.default.resolve('server.js');"`, + ) +}) + +test('whitespace between imports does not trigger hoisting', async () => { + expect( + await ssrTransformSimpleCode( + `import { dirname } from 'node:path';\n\n\nimport fs from 'node:fs';`, + ), + ).toMatchInlineSnapshot(` + "const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname"]}); + + + const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});" + `) +}) + +test('preserve line offset when rewriting imports', async () => { + // The line number of each non-import statement must not change. + const inputLines = [ + `debugger;`, + ``, + `import {`, + ` dirname,`, + ` join,`, + `} from 'node:path';`, + ``, + `debugger;`, + ``, + `import fs from 'node:fs';`, + ``, + `debugger;`, + ``, + `import {`, + ` red,`, + ` green,`, + `} from 'kleur/colors';`, + ``, + `debugger;`, + ] + + const output = await ssrTransformSimpleCode(inputLines.join('\n')) + expect(output).toBeDefined() + + const outputLines = output!.split('\n') + expect( + outputLines + .map((line, i) => `${String(i + 1).padStart(2)} | ${line}`.trimEnd()) + .join('\n'), + ).toMatchInlineSnapshot(` + " 1 | const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname","join"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});const __vite_ssr_import_2__ = await __vite_ssr_import__("kleur/colors", {"importedNames":["red","green"]});debugger; + 2 | + 3 | + 4 | + 5 | + 6 | + 7 | + 8 | debugger; + 9 | + 10 | + 11 | + 12 | debugger; + 13 | + 14 | + 15 | + 16 | + 17 | + 18 | + 19 | debugger;" + `) + + // Ensure the debugger statements are still on the same lines. + expect(outputLines[0].endsWith(inputLines[0])).toBe(true) + expect(outputLines[7]).toBe(inputLines[7]) + expect(outputLines[11]).toBe(inputLines[11]) + expect(outputLines[18]).toBe(inputLines[18]) +}) + +// not implemented +test.skip('comments between imports do not trigger hoisting', async () => { + expect( + await ssrTransformSimpleCode( + `import { dirname } from 'node:path';// comment\nimport fs from 'node:fs';`, + ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["default"]}); - __vite_ssr_import_0__.default.resolve('server.js');" + "const __vite_ssr_import_0__ = await __vite_ssr_import__("node:path", {"importedNames":["dirname"]});// comment + const __vite_ssr_import_1__ = await __vite_ssr_import__("node:fs", {"importedNames":["default"]});" `) }) @@ -220,10 +292,9 @@ test('do not rewrite method definition', async () => { const result = await ssrTransformSimple( `import { fn } from 'vue';class A { fn() { fn() } }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - class A { fn() { (0,__vite_ssr_import_0__.fn)() } }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});class A { fn() { (0,__vite_ssr_import_0__.fn)() } }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -231,10 +302,9 @@ test('do not rewrite when variable is in scope', async () => { const result = await ssrTransformSimple( `import { fn } from 'vue';function A(){ const fn = () => {}; return { fn }; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - function A(){ const fn = () => {}; return { fn }; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ const fn = () => {}; return { fn }; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -243,10 +313,9 @@ test('do not rewrite when variable is in scope with object destructuring', async const result = await ssrTransformSimple( `import { fn } from 'vue';function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -255,10 +324,9 @@ test('do not rewrite when variable is in scope with array destructuring', async const result = await ssrTransformSimple( `import { fn } from 'vue';function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -267,10 +335,9 @@ test('rewrite variable in string interpolation in function nested arguments', as const result = await ssrTransformSimple( `import { fn } from 'vue';function A({foo = \`test\${fn}\`} = {}){ return {}; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - function A({foo = \`test\${__vite_ssr_import_0__.fn}\`} = {}){ return {}; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A({foo = \`test\${__vite_ssr_import_0__.fn}\`} = {}){ return {}; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -279,10 +346,9 @@ test('rewrite variables in default value of destructuring params', async () => { const result = await ssrTransformSimple( `import { fn } from 'vue';function A({foo = fn}){ return {}; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - function A({foo = __vite_ssr_import_0__.fn}){ return {}; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A({foo = __vite_ssr_import_0__.fn}){ return {}; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -290,10 +356,9 @@ test('do not rewrite when function declaration is in scope', async () => { const result = await ssrTransformSimple( `import { fn } from 'vue';function A(){ function fn() {}; return { fn }; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]}); - function A(){ function fn() {}; return { fn }; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["fn"]});function A(){ function fn() {}; return { fn }; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -302,10 +367,9 @@ test('do not rewrite when function expression is in scope', async () => { const result = await ssrTransformSimple( `import {fn} from './vue';var a = function() { return function fn() { console.log(fn) } }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]}); - var a = function() { return function fn() { console.log(fn) } }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]});var a = function() { return function fn() { console.log(fn) } }"`, + ) }) // #16452 @@ -313,20 +377,18 @@ test('do not rewrite when function expression is in global scope', async () => { const result = await ssrTransformSimple( `import {fn} from './vue';foo(function fn(a = fn) { console.log(fn) })`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]}); - foo(function fn(a = fn) { console.log(fn) })" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["fn"]});foo(function fn(a = fn) { console.log(fn) })"`, + ) }) test('do not rewrite when class declaration is in scope', async () => { const result = await ssrTransformSimple( `import { cls } from 'vue';function A(){ class cls {} return { cls }; }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["cls"]}); - function A(){ class cls {} return { cls }; }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["cls"]});function A(){ class cls {} return { cls }; }"`, + ) expect(result?.deps).toEqual(['vue']) }) @@ -334,30 +396,27 @@ test('do not rewrite when class expression is in scope', async () => { const result = await ssrTransformSimple( `import { cls } from './vue';var a = function() { return class cls { constructor() { console.log(cls) } } }`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]}); - var a = function() { return class cls { constructor() { console.log(cls) } } }" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]});var a = function() { return class cls { constructor() { console.log(cls) } } }"`, + ) }) test('do not rewrite when class expression is in global scope', async () => { const result = await ssrTransformSimple( `import { cls } from './vue';foo(class cls { constructor() { console.log(cls) } })`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]}); - foo(class cls { constructor() { console.log(cls) } })" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("./vue", {"importedNames":["cls"]});foo(class cls { constructor() { console.log(cls) } })"`, + ) }) test('do not rewrite catch clause', async () => { const result = await ssrTransformSimple( `import {error} from './dependency';try {} catch(error) {}`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["error"]}); - try {} catch(error) {}" - `) + expect(result?.code).toMatchInlineSnapshot( + `"const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["error"]});try {} catch(error) {}"`, + ) expect(result?.deps).toEqual(['./dependency']) }) @@ -368,8 +427,7 @@ test('should declare variable for imported super class', async () => { `import { Foo } from './dependency';` + `class A extends Foo {}`, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]}); - const Foo = __vite_ssr_import_0__.Foo; + "const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});const Foo = __vite_ssr_import_0__.Foo; class A extends Foo {}" `) @@ -382,8 +440,7 @@ test('should declare variable for imported super class', async () => { `export class B extends Foo {}`, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]}); - const Foo = __vite_ssr_import_0__.Foo; + "const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});const Foo = __vite_ssr_import_0__.Foo; class A extends Foo {}; class B extends Foo {} Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ return B }}); @@ -448,9 +505,7 @@ test('sourcemap is correct for hoisted imports', async () => { const result = (await ssrTransform(code, null, 'input.js', code))! expect(result.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["foo"]}); - const __vite_ssr_import_1__ = await __vite_ssr_import__("vue2", {"importedNames":["bar"]}); - + "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["foo"]});const __vite_ssr_import_1__ = await __vite_ssr_import__("vue2", {"importedNames":["bar"]}); console.log((0,__vite_ssr_import_0__.foo), (0,__vite_ssr_import_1__.bar)); @@ -465,7 +520,7 @@ test('sourcemap is correct for hoisted imports', async () => { column: 0, name: null, }) - expect(originalPositionFor(traceMap, { line: 2, column: 0 })).toStrictEqual({ + expect(originalPositionFor(traceMap, { line: 1, column: 90 })).toStrictEqual({ source: 'input.js', line: 6, column: 0, @@ -532,8 +587,7 @@ test('overwrite bindings', async () => { `function g() { const f = () => { const inject = true }; console.log(inject) }\n`, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["inject"]}); - const a = { inject: __vite_ssr_import_0__.inject }; + "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["inject"]});const a = { inject: __vite_ssr_import_0__.inject }; const b = { test: __vite_ssr_import_0__.inject }; function c() { const { test: inject } = { test: true }; console.log(inject) } const d = __vite_ssr_import_0__.inject; @@ -561,9 +615,8 @@ function c({ _ = bar() + foo() }) {} `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo","bar"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo","bar"]}); const a = ({ _ = (0,__vite_ssr_import_0__.foo)() }) => {}; function b({ _ = (0,__vite_ssr_import_0__.bar)() }) {} function c({ _ = (0,__vite_ssr_import_0__.bar)() + (0,__vite_ssr_import_0__.foo)() }) {} @@ -583,9 +636,8 @@ const a = () => { `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n"]}); const a = () => { const { type: n = 'bar' } = {}; console.log(n) @@ -606,9 +658,8 @@ const foo = {} `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n","m"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["n","m"]}); const foo = {}; { @@ -649,9 +700,8 @@ objRest() `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add","get","set","rest","objRest"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add","get","set","rest","objRest"]}); function a() { const { @@ -699,9 +749,8 @@ const obj = { `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]}); const bar = 'bar'; @@ -731,9 +780,8 @@ class A { `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["remove","add"]}); const add = __vite_ssr_import_0__.add; const remove = __vite_ssr_import_0__.remove; @@ -763,9 +811,8 @@ class A { `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]}); const bar = 'bar'; @@ -809,9 +856,8 @@ bbb() `, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["aaa","bbb","ccc","ddd"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["aaa","bbb","ccc","ddd"]}); function foobar() { ddd(); @@ -856,8 +902,6 @@ test('jsx', async () => { .toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("react", {"importedNames":["default"]}); const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["Foo","Slot"]}); - - function Bar({ Slot: Slot2 = /* @__PURE__ */ __vite_ssr_import_0__.default.createElement((0,__vite_ssr_import_1__.Foo), null) }) { return /* @__PURE__ */ __vite_ssr_import_0__.default.createElement(__vite_ssr_import_0__.default.Fragment, null, /* @__PURE__ */ __vite_ssr_import_0__.default.createElement(Slot2, null)); } @@ -930,8 +974,7 @@ import foo from "foo"`, ), ).toMatchInlineSnapshot(` "#!/usr/bin/env node - const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]}); - console.log((0,__vite_ssr_import_0__.default)); + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["default"]});console.log((0,__vite_ssr_import_0__.default)); " `) }) @@ -946,7 +989,6 @@ foo()`, ).toMatchInlineSnapshot(` "#!/usr/bin/env node const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); - (0,__vite_ssr_import_0__.foo)()" `) }) @@ -982,7 +1024,6 @@ export class Test { expect(await ssrTransformSimpleCode(code)).toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]}); - if (false) { const foo = 'foo'; console.log(foo) @@ -1023,9 +1064,8 @@ function test() { return [foo, bar] }`), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]}); function test() { if (true) { var foo = () => { var why = 'would' }, bar = 'someone' @@ -1050,9 +1090,8 @@ function test() { return bar; }`), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar","baz"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar","baz"]}); function test() { [__vite_ssr_import_0__.foo]; { @@ -1082,9 +1121,8 @@ for (const test in tests) { console.log(test) }`), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./test.js", {"importedNames":["test"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("./test.js", {"importedNames":["test"]}); for (const test of tests) { console.log(test) @@ -1114,9 +1152,8 @@ const Baz = class extends Foo {} `, ) expect(result?.code).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["default","Bar"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["default","Bar"]}); console.log((0,__vite_ssr_import_0__.default), (0,__vite_ssr_import_0__.Bar)); const obj = { @@ -1135,9 +1172,8 @@ test('import assertion attribute', async () => { import('./bar.json', { with: { type: 'json' } }); `), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo.json"); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo.json"); __vite_ssr_dynamic_import__('./bar.json', { with: { type: 'json' } }); " `) @@ -1157,14 +1193,11 @@ console.log(foo + 2) `), ).toMatchInlineSnapshot(` "const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]}); - console.log(__vite_ssr_import_0__.foo + 1); - const __vite_ssr_import_1__ = await __vite_ssr_import__("./a"); - __vite_ssr_exportAll__(__vite_ssr_import_1__); + const __vite_ssr_import_1__ = await __vite_ssr_import__("./a");__vite_ssr_exportAll__(__vite_ssr_import_1__); ; - const __vite_ssr_import_2__ = await __vite_ssr_import__("./b"); - __vite_ssr_exportAll__(__vite_ssr_import_2__); + const __vite_ssr_import_2__ = await __vite_ssr_import__("./b");__vite_ssr_exportAll__(__vite_ssr_import_2__); ; console.log(__vite_ssr_import_0__.foo + 2) " @@ -1180,12 +1213,10 @@ export * as bar from './bar' console.log(bar) `), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]}); __vite_ssr_exports__.default = (0,__vite_ssr_import_0__.foo)(); const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar"); - Object.defineProperty(__vite_ssr_exports__, "bar", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }});; console.log(bar) " @@ -1256,9 +1287,8 @@ switch (1) { } `), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./f", {"importedNames":["f"]}); - - + " + const __vite_ssr_import_0__ = await __vite_ssr_import__("./f", {"importedNames":["f"]}); let x = 0; diff --git a/packages/vite/src/node/ssr/fetchModule.ts b/packages/vite/src/node/ssr/fetchModule.ts index d3851b37e7053c..a658fe6c3e31bd 100644 --- a/packages/vite/src/node/ssr/fetchModule.ts +++ b/packages/vite/src/node/ssr/fetchModule.ts @@ -33,13 +33,16 @@ export async function fetchModule( return { externalize: url, type: 'builtin' } } - if (isExternalUrl(url)) { + // handle file urls from not statically analyzable dynamic import + const isFileUrl = url.startsWith('file://') + + if (isExternalUrl(url) && !isFileUrl) { return { externalize: url, type: 'network' } } // if there is no importer, the file is an entry point // entry points are always internalized - if (importer && url[0] !== '.' && url[0] !== '/') { + if (!isFileUrl && importer && url[0] !== '.' && url[0] !== '/') { const { isProduction, root } = environment.config const { externalConditions, dedupe, preserveSymlinks } = environment.config.resolve @@ -74,7 +77,7 @@ export async function fetchModule( // this is an entry point module, very high chance it's not resolved yet // for example: runner.import('./some-file') or runner.import('/some-file') - if (!importer) { + if (isFileUrl || !importer) { const resolved = await environment.pluginContainer.resolveId(url) if (!resolved) { throw new Error(`[vite] cannot find entry point module '${url}'.`) @@ -84,8 +87,8 @@ export async function fetchModule( url = unwrapId(url) - let mod = await environment.moduleGraph.getModuleByUrl(url) - const cached = !!mod?.transformResult + const mod = await environment.moduleGraph.ensureEntryFromUrl(url) + const cached = !!mod.transformResult // if url is already cached, we can just confirm it's also cached on the server if (options.cached && cached) { @@ -102,17 +105,6 @@ export async function fetchModule( ) } - // module entry should be created by transformRequest - mod ??= await environment.moduleGraph.getModuleByUrl(url) - - if (!mod) { - throw new Error( - `[vite] cannot find module '${url}' ${ - importer ? ` imported from '${importer}'` : '' - }.`, - ) - } - if (options.inlineSourceMap !== false) { result = inlineSourceMap(mod, result, options.startOffset) } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/dynamic-import.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/dynamic-import.js index 457a1b723bf7ce..643f42afc87c80 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/dynamic-import.js +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/dynamic-import.js @@ -1,16 +1,20 @@ import path from 'node:path' import * as staticModule from './simple' +import { pathToFileURL } from 'node:url' export const initialize = async () => { const nameRelative = './simple' const nameAbsolute = '/fixtures/simple' const nameAbsoluteExtension = '/fixtures/simple.js' + const absolutePath = path.join(import.meta.dirname, "simple.js") + const fileUrl = pathToFileURL(absolutePath) return { dynamicProcessed: await import('./simple'), dynamicRelative: await import(nameRelative), dynamicAbsolute: await import(nameAbsolute), dynamicAbsoluteExtension: await import(nameAbsoluteExtension), - dynamicAbsoluteFull: await import(path.join(import.meta.dirname, "simple.js")), + dynamicAbsoluteFull: await import((process.platform === 'win32' ? '/@fs/' : '') + absolutePath), + dynamicFileUrl: await import(fileUrl), static: staticModule, } } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.invoke.mjs b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.invoke.mjs index 3e843293a513f6..fe65a1bb4e11ba 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.invoke.mjs +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.invoke.mjs @@ -1,7 +1,6 @@ // @ts-check import { BroadcastChannel, parentPort } from 'node:worker_threads' -import { fileURLToPath } from 'node:url' import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner' import { createBirpc } from 'birpc' @@ -20,7 +19,6 @@ const rpc = createBirpc({}, { const runner = new ModuleRunner( { - root: fileURLToPath(new URL('./', import.meta.url)), transport: { invoke(data) { return rpc.invoke(data) } }, diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.mjs b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.mjs index 3e00f5ff67c58c..9a2536efe0f2d0 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.mjs +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/worker.mjs @@ -1,7 +1,6 @@ // @ts-check import { BroadcastChannel, parentPort } from 'node:worker_threads' -import { fileURLToPath } from 'node:url' import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner' if (!parentPort) { @@ -24,7 +23,6 @@ const messagePortTransport = { const runner = new ModuleRunner( { - root: fileURLToPath(new URL('./', import.meta.url)), transport: messagePortTransport, }, new ESModulesEvaluator(), diff --git a/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts b/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts index 49a7bdb2356fe9..0dcf439589e150 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts +++ b/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts @@ -178,6 +178,7 @@ describe('module runner initialization', async () => { expect(modules.static).toBe(modules.dynamicAbsolute) expect(modules.static).toBe(modules.dynamicAbsoluteExtension) expect(modules.static).toBe(modules.dynamicAbsoluteFull) + expect(modules.static).toBe(modules.dynamicFileUrl) }) it('correctly imports a virtual module', async ({ runner }) => { @@ -263,3 +264,41 @@ describe('optimize-deps', async () => { expect(mod.default.hello()).toMatchInlineSnapshot(`"world"`) }) }) + +describe('resolveId absolute path entry', async () => { + const it = await createModuleRunnerTester({ + plugins: [ + { + name: 'test-resolevId', + enforce: 'pre', + resolveId(source) { + if ( + source === + posix.join(this.environment.config.root, 'fixtures/basic.js') + ) { + return '\0virtual:basic' + } + }, + load(id) { + if (id === '\0virtual:basic') { + return `export const name = "virtual:basic"` + } + }, + }, + ], + }) + + it('ssrLoadModule', async ({ server }) => { + const mod = await server.ssrLoadModule( + posix.join(server.config.root, 'fixtures/basic.js'), + ) + expect(mod.name).toMatchInlineSnapshot(`"virtual:basic"`) + }) + + it('runner', async ({ server, runner }) => { + const mod = await runner.import( + posix.join(server.config.root, 'fixtures/basic.js'), + ) + expect(mod.name).toMatchInlineSnapshot(`"virtual:basic"`) + }) +}) diff --git a/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts b/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts index 758072f25a83be..b519ee4dd0a7ee 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts +++ b/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts @@ -1,5 +1,5 @@ import { describe, expect } from 'vitest' -import type { ModuleRunner } from 'vite/module-runner' +import type { ViteDevServer } from '../../..' import { createModuleRunnerTester, editFile, resolvePath } from './utils' describe('module runner initialization', async () => { @@ -18,13 +18,13 @@ describe('module runner initialization', async () => { return err } } - const serializeStack = (runner: ModuleRunner, err: Error) => { - return err.stack!.split('\n')[1].replace(runner.options.root, '') + const serializeStack = (server: ViteDevServer, err: Error) => { + return err.stack!.split('\n')[1].replace(server.config.root, '') } - const serializeStackDeep = (runtime: ModuleRunner, err: Error) => { + const serializeStackDeep = (server: ViteDevServer, err: Error) => { return err .stack!.split('\n') - .map((s) => s.replace(runtime.options.root, '')) + .map((s) => s.replace(server.config.root, '')) } it('source maps are correctly applied to stack traces', async ({ @@ -35,7 +35,7 @@ describe('module runner initialization', async () => { const topLevelError = await getError(() => runner.import('/fixtures/has-error.js'), ) - expect(serializeStack(runner, topLevelError)).toBe( + expect(serializeStack(server, topLevelError)).toBe( ' at /fixtures/has-error.js:2:7', ) @@ -43,7 +43,7 @@ describe('module runner initialization', async () => { const mod = await runner.import('/fixtures/throws-error-method.ts') mod.throwError() }) - expect(serializeStack(runner, methodError)).toBe( + expect(serializeStack(server, methodError)).toBe( ' at Module.throwError (/fixtures/throws-error-method.ts:6:9)', ) @@ -60,17 +60,17 @@ describe('module runner initialization', async () => { mod.throwError() }) - expect(serializeStack(runner, methodErrorNew)).toBe( + expect(serializeStack(server, methodErrorNew)).toBe( ' at Module.throwError (/fixtures/throws-error-method.ts:11:9)', ) }) - it('deep stacktrace', async ({ runner }) => { + it('deep stacktrace', async ({ runner, server }) => { const methodError = await getError(async () => { const mod = await runner.import('/fixtures/has-error-deep.ts') mod.main() }) - expect(serializeStackDeep(runner, methodError).slice(0, 3)).toEqual([ + expect(serializeStackDeep(server, methodError).slice(0, 3)).toEqual([ 'Error: crash', ' at crash (/fixtures/has-error-deep.ts:2:9)', ' at Module.main (/fixtures/has-error-deep.ts:6:3)', diff --git a/packages/vite/src/node/ssr/runtime/__tests__/utils.ts b/packages/vite/src/node/ssr/runtime/__tests__/utils.ts index 7ab3a40fbfef8e..7ac4b1f19d8e34 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/utils.ts +++ b/packages/vite/src/node/ssr/runtime/__tests__/utils.ts @@ -71,6 +71,7 @@ export async function createModuleRunnerTester( } }, }, + ...(config.plugins ?? []), ], ...config, }) diff --git a/packages/vite/src/node/ssr/ssrTransform.ts b/packages/vite/src/node/ssr/ssrTransform.ts index c4552f73229663..1294ece439f1c4 100644 --- a/packages/vite/src/node/ssr/ssrTransform.ts +++ b/packages/vite/src/node/ssr/ssrTransform.ts @@ -126,32 +126,53 @@ async function ssrTransformScript( ) { const source = importNode.source.value as string deps.add(source) - const importId = `__vite_ssr_import_${uid++}__` // Reduce metadata to undefined if it's all default values - if ( - metadata && - (metadata.importedNames == null || metadata.importedNames.length === 0) - ) { - metadata = undefined - } - const metadataStr = metadata ? `, ${JSON.stringify(metadata)}` : '' - - s.update( - importNode.start, - importNode.end, - `const ${importId} = await ${ssrImportKey}(${JSON.stringify( - source, - )}${metadataStr});\n`, - ) + const metadataArg = + (metadata?.importedNames?.length ?? 0) > 0 + ? `, ${JSON.stringify(metadata)}` + : '' - if (importNode.start === index) { - // no need to hoist, but update hoistIndex to keep the order - hoistIndex = importNode.end - } else { - // There will be an error if the module is called before it is imported, - // so the module import statement is hoisted to the top + const importId = `__vite_ssr_import_${uid++}__` + const transformedImport = `const ${importId} = await ${ssrImportKey}(${JSON.stringify( + source, + )}${metadataArg});` + + s.update(importNode.start, importNode.end, transformedImport) + + // If there's only whitespace characters between the last import and the + // current one, that means there's no statements between them and + // hoisting is not needed. + // FIXME: account for comments between imports + const nonWhitespaceRegex = /\S/g + nonWhitespaceRegex.lastIndex = index + nonWhitespaceRegex.exec(code) + if (importNode.start > nonWhitespaceRegex.lastIndex) { + // Imports are moved to the top of the file (AKA “hoisting”) to ensure any + // non-import statements before them are executed after the import. This + // aligns SSR imports with native ESM import behavior. s.move(importNode.start, importNode.end, index) + } else { + // Only update hoistIndex when *not* hoisting the current import. This + // ensures that once any import in this module has been hoisted, all + // remaining imports will also be hoisted. This is inherently true because + // we work from the top of the file downward. + hoistIndex = importNode.end + } + + // Track how many lines the original import statement spans, so we can + // preserve the line offset. + let linesSpanned = 1 + for (let i = importNode.start; i < importNode.end; i++) { + if (code[i] === '\n') { + linesSpanned++ + } + } + if (linesSpanned > 1) { + // This leaves behind any extra newlines that were removed during + // transformation, in the position of the original import statement + // (before any hoisting). + s.prependRight(importNode.end, '\n'.repeat(linesSpanned - 1)) } return importId diff --git a/packages/vite/types/internal/cssPreprocessorOptions.d.ts b/packages/vite/types/internal/cssPreprocessorOptions.d.ts index de962a8851dea8..6209d4291b4670 100644 --- a/packages/vite/types/internal/cssPreprocessorOptions.d.ts +++ b/packages/vite/types/internal/cssPreprocessorOptions.d.ts @@ -6,7 +6,7 @@ import type DartSass from 'sass' import type SassEmbedded from 'sass-embedded' // @ts-ignore `less` may not be installed import type Less from 'less' -// @ts-ignore `less` may not be installed +// @ts-ignore `stylus` may not be installed import type Stylus from 'stylus' /* eslint-enable @typescript-eslint/ban-ts-comment */ diff --git a/packages/vite/types/internal/lightningcssOptions.d.ts b/packages/vite/types/internal/lightningcssOptions.d.ts index 57e6820f369dfa..48d71148f727d3 100644 --- a/packages/vite/types/internal/lightningcssOptions.d.ts +++ b/packages/vite/types/internal/lightningcssOptions.d.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -// @ts-ignore `sass` may not be installed +// @ts-ignore `lightningcss` may not be installed import type Lightningcss from 'lightningcss' /* eslint-enable @typescript-eslint/ban-ts-comment */ diff --git a/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts b/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts index 538b93a7ebdacb..9dec9ebd992f68 100644 --- a/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts +++ b/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts @@ -61,7 +61,7 @@ test('css modules', async () => { test('inline css modules', async () => { const css = await page.textContent('.modules-inline') - expect(css).toMatch(/\.\w{6}_apply-color-inline/) + expect(css).toMatch(/\._?\w{6}_apply-color-inline/) }) test.runIf(isBuild)('minify css', async () => { diff --git a/playground/resolve/__tests__/resolve.spec.ts b/playground/resolve/__tests__/resolve.spec.ts index e12ddecedb5c0d..ba5e6441dc7edb 100644 --- a/playground/resolve/__tests__/resolve.spec.ts +++ b/playground/resolve/__tests__/resolve.spec.ts @@ -248,3 +248,7 @@ test.runIf(isBuild)('public dir is not copied', async () => { fs.existsSync(path.resolve(testDir, 'dist/should-not-be-copied')), ).toBe(false) }) + +test('import utf8-bom package', async () => { + expect(await page.textContent('.utf8-bom-package')).toMatch('[success]') +}) diff --git a/playground/resolve/index.html b/playground/resolve/index.html index 8c315f74dcd2c1..f1480b0a9b3e52 100644 --- a/playground/resolve/index.html +++ b/playground/resolve/index.html @@ -176,6 +176,9 @@

resolve package that contains # in path

resolve non normalized absolute path

+

utf8-bom-package

+

fail

+