Skip to content

Commit 5786837

Browse files
authored
feat!: remove ssr proxy for externalized modules (#14521)
1 parent 7acb016 commit 5786837

File tree

8 files changed

+240
-96
lines changed

8 files changed

+240
-96
lines changed

docs/guide/migration.md

+26
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,32 @@ See the [troubleshooting guide](/guide/troubleshooting.html#vite-cjs-node-api-de
3434

3535
## General Changes
3636

37+
### SSR externalized modules value now matches production
38+
39+
In Vite 4, SSR externalized modules are wrapped with `.default` and `.__esModule` handling for better interoperability, but it doesn't match the production behaviour when loaded by the runtime environment (e.g. Node.js), causing hard-to-catch inconsistencies. By default, all direct project dependencies are SSR externalized.
40+
41+
Vite 5 now removes the `.default` and `.__esModule` handling to match the production behaviour. In practice, this shouldn't affect properly-packaged dependencies, but if you encounter new issues loading modules, you can try these refactors:
42+
43+
```js
44+
// Before:
45+
import { foo } from 'bar'
46+
47+
// After:
48+
import _bar from 'bar'
49+
const { foo } = _bar
50+
```
51+
52+
```js
53+
// Before:
54+
import foo from 'bar'
55+
56+
// After:
57+
import * as _foo from 'bar'
58+
const foo = _foo.default
59+
```
60+
61+
Note that these changes matches the Node.js behaviour, so you can also run the imports in Node.js to test it out. If you prefer to stick with the previous behaviour, you can set `legacy.proxySsrExternalModules` to `true`.
62+
3763
### `worker.plugins` is now a function
3864

3965
In Vite 4, `worker.plugins` accepted an array of plugins (`(Plugin | Plugin[])[]`). From Vite 5, it needs to be configured as a function that returns an array of plugins (`() => (Plugin | Plugin[])[]`). This change is required so parallel worker builds run more consistently and predictably.

packages/vite/src/node/config.ts

+16-28
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ import {
3232
createFilter,
3333
isBuiltin,
3434
isExternalUrl,
35+
isFilePathESM,
3536
isNodeBuiltin,
3637
isObject,
37-
lookupFile,
3838
mergeAlias,
3939
mergeConfig,
4040
normalizeAlias,
@@ -314,8 +314,16 @@ export interface ExperimentalOptions {
314314

315315
export interface LegacyOptions {
316316
/**
317-
* No longer needed for now, but kept for backwards compatibility.
317+
* In Vite 4, SSR-externalized modules (modules not bundled and loaded by Node.js at runtime)
318+
* are implicitly proxied in dev to automatically handle `default` and `__esModule` access.
319+
* However, this does not correctly reflect how it works in the Node.js runtime, causing
320+
* inconsistencies between dev and prod.
321+
*
322+
* In Vite 5, the proxy is removed so dev and prod are consistent, but if you still require
323+
* the old behaviour, you can enable this option. If so, please leave your feedback at
324+
* https://github.com/vitejs/vite/discussions/14697.
318325
*/
326+
proxySsrExternalModules?: boolean
319327
}
320328

321329
export interface ResolvedWorkerOptions {
@@ -978,19 +986,7 @@ export async function loadConfigFromFile(
978986
return null
979987
}
980988

981-
let isESM = false
982-
if (/\.m[jt]s$/.test(resolvedPath)) {
983-
isESM = true
984-
} else if (/\.c[jt]s$/.test(resolvedPath)) {
985-
isESM = false
986-
} else {
987-
// check package.json for type: "module" and set `isESM` to true
988-
try {
989-
const pkg = lookupFile(configRoot, ['package.json'])
990-
isESM =
991-
!!pkg && JSON.parse(fs.readFileSync(pkg, 'utf-8')).type === 'module'
992-
} catch (e) {}
993-
}
989+
const isESM = isFilePathESM(resolvedPath)
994990

995991
try {
996992
const bundled = await bundleConfigFile(resolvedPath, isESM)
@@ -1076,18 +1072,6 @@ async function bundleConfigFile(
10761072
false,
10771073
)?.id
10781074
}
1079-
const isESMFile = (id: string): boolean => {
1080-
if (id.endsWith('.mjs')) return true
1081-
if (id.endsWith('.cjs')) return false
1082-
1083-
const nearestPackageJson = findNearestPackageData(
1084-
path.dirname(id),
1085-
packageCache,
1086-
)
1087-
return (
1088-
!!nearestPackageJson && nearestPackageJson.data.type === 'module'
1089-
)
1090-
}
10911075

10921076
// externalize bare imports
10931077
build.onResolve(
@@ -1135,7 +1119,11 @@ async function bundleConfigFile(
11351119
if (idFsPath && isImport) {
11361120
idFsPath = pathToFileURL(idFsPath).href
11371121
}
1138-
if (idFsPath && !isImport && isESMFile(idFsPath)) {
1122+
if (
1123+
idFsPath &&
1124+
!isImport &&
1125+
isFilePathESM(idFsPath, packageCache)
1126+
) {
11391127
throw new Error(
11401128
`${JSON.stringify(
11411129
id,

packages/vite/src/node/plugins/resolve.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
isBuiltin,
2727
isDataUrl,
2828
isExternalUrl,
29+
isFilePathESM,
2930
isInNodeModules,
3031
isNonDriveRelativeAbsolutePath,
3132
isObject,
@@ -822,8 +823,6 @@ export function tryNodeResolve(
822823
})
823824
}
824825

825-
const ext = path.extname(resolved)
826-
827826
if (
828827
!options.ssrOptimizeCheck &&
829828
(!isInNodeModules(resolved) || // linked
@@ -859,12 +858,7 @@ export function tryNodeResolve(
859858
(!options.ssrOptimizeCheck && !isBuild && ssr) ||
860859
// Only optimize non-external CJS deps during SSR by default
861860
(ssr &&
862-
!(
863-
ext === '.cjs' ||
864-
(ext === '.js' &&
865-
findNearestPackageData(path.dirname(resolved), options.packageCache)
866-
?.data.type !== 'module')
867-
) &&
861+
isFilePathESM(resolved, options.packageCache) &&
868862
!(include?.includes(pkgId) || include?.includes(id)))
869863

870864
if (options.ssrOptimizeCheck) {

0 commit comments

Comments
 (0)