Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: remove ssr proxy for externalized modules #14521

Merged
merged 10 commits into from
Oct 19, 2023
26 changes: 26 additions & 0 deletions docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ See the [troubleshooting guide](/guide/troubleshooting.html#vite-cjs-node-api-de

## General Changes

### SSR externalized modules value now matches production

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.

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:

```js
// Before:
import { foo } from 'bar'

// After:
import _bar from 'bar'
const { foo } = _bar
```

```js
// Before:
import foo from 'bar'

// After:
import * as _foo from 'bar'
const foo = _foo.default
```

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`.

### `worker.plugins` is now a function

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.
Expand Down
44 changes: 16 additions & 28 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import {
createFilter,
isBuiltin,
isExternalUrl,
isFilePathESM,
isNodeBuiltin,
isObject,
lookupFile,
mergeAlias,
mergeConfig,
normalizeAlias,
Expand Down Expand Up @@ -314,8 +314,16 @@ export interface ExperimentalOptions {

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

export interface ResolvedWorkerOptions {
Expand Down Expand Up @@ -978,19 +986,7 @@ export async function loadConfigFromFile(
return null
}

let isESM = false
if (/\.m[jt]s$/.test(resolvedPath)) {
isESM = true
} else if (/\.c[jt]s$/.test(resolvedPath)) {
isESM = false
} else {
// check package.json for type: "module" and set `isESM` to true
try {
const pkg = lookupFile(configRoot, ['package.json'])
isESM =
!!pkg && JSON.parse(fs.readFileSync(pkg, 'utf-8')).type === 'module'
} catch (e) {}
}
const isESM = isFilePathESM(resolvedPath)

try {
const bundled = await bundleConfigFile(resolvedPath, isESM)
Expand Down Expand Up @@ -1076,18 +1072,6 @@ async function bundleConfigFile(
false,
)?.id
}
const isESMFile = (id: string): boolean => {
if (id.endsWith('.mjs')) return true
if (id.endsWith('.cjs')) return false

const nearestPackageJson = findNearestPackageData(
path.dirname(id),
packageCache,
)
return (
!!nearestPackageJson && nearestPackageJson.data.type === 'module'
)
}

// externalize bare imports
build.onResolve(
Expand Down Expand Up @@ -1135,7 +1119,11 @@ async function bundleConfigFile(
if (idFsPath && isImport) {
idFsPath = pathToFileURL(idFsPath).href
}
if (idFsPath && !isImport && isESMFile(idFsPath)) {
if (
idFsPath &&
!isImport &&
isFilePathESM(idFsPath, packageCache)
) {
throw new Error(
`${JSON.stringify(
id,
Expand Down
10 changes: 2 additions & 8 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
isBuiltin,
isDataUrl,
isExternalUrl,
isFilePathESM,
isInNodeModules,
isNonDriveRelativeAbsolutePath,
isObject,
Expand Down Expand Up @@ -822,8 +823,6 @@ export function tryNodeResolve(
})
}

const ext = path.extname(resolved)

if (
!options.ssrOptimizeCheck &&
(!isInNodeModules(resolved) || // linked
Expand Down Expand Up @@ -859,12 +858,7 @@ export function tryNodeResolve(
(!options.ssrOptimizeCheck && !isBuild && ssr) ||
// Only optimize non-external CJS deps during SSR by default
(ssr &&
!(
ext === '.cjs' ||
(ext === '.js' &&
findNearestPackageData(path.dirname(resolved), options.packageCache)
?.data.type !== 'module')
) &&
isFilePathESM(resolved, options.packageCache) &&
!(include?.includes(pkgId) || include?.includes(id)))

if (options.ssrOptimizeCheck) {
Expand Down
Loading