Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/vite/src/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export const DEFAULT_SERVER_CONDITIONS = Object.freeze(
DEFAULT_CONDITIONS.filter((c) => c !== 'browser'),
)

export const DEFAULT_EXTERNAL_CONDITIONS = Object.freeze(['node'])
export const DEFAULT_EXTERNAL_CONDITIONS = Object.freeze([
'node',
'module-sync',
])

/**
* The browser versions that are included in the Baseline Widely Available on 2025-05-01.
Expand Down
26 changes: 22 additions & 4 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ export function tryNodeResolve(
const resolveId = deepMatch ? resolveDeepImport : resolvePackageEntry
const unresolvedId = deepMatch ? '.' + id.slice(pkgId.length) : id

let resolved = resolveId(unresolvedId, pkg, options)
let resolved = resolveId(unresolvedId, pkg, options, externalize)
if (!resolved) {
return
}
Expand Down Expand Up @@ -895,6 +895,7 @@ export function resolvePackageEntry(
id: string,
{ dir, data, setResolvedCache, getResolvedCache }: PackageData,
options: InternalResolveOptions,
externalize?: boolean,
): string | undefined {
const { file: idWithoutPostfix, postfix } = splitFileAndPostfix(id)

Expand All @@ -909,7 +910,13 @@ export function resolvePackageEntry(
// resolve exports field with highest priority
// using https://github.com/lukeed/resolve.exports
if (data.exports) {
entryPoint = resolveExportsOrImports(data, '.', options, 'exports')
entryPoint = resolveExportsOrImports(
data,
'.',
options,
'exports',
externalize,
)
}

// fallback to mainFields if still not resolved
Expand Down Expand Up @@ -989,8 +996,12 @@ function resolveExportsOrImports(
key: string,
options: InternalResolveOptions,
type: 'imports' | 'exports',
externalize?: boolean,
) {
const conditions = options.conditions.map((condition) => {
const rawConditions = externalize
? options.externalConditions
: options.conditions
const conditions = rawConditions.map((condition) => {
Comment on lines +1001 to +1004
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bug fix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

If it is determined as external here,

const external =
options.externalize &&
options.isBuild &&
currentEnvironmentOptions.consumer === 'server' &&
shouldExternalize(this.environment, id, importer)

the resolution here should use externalConditions.
(res = tryNodeResolve(id, importer, options, depsOptimizer, external))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, it's only for build? Might not be relevant to this PR specifically but curious what's the idea for options.isBuild check? Is this because Vite crawl imports by ourselves by import analysis resolve (and not resolve for external) during dev, but this is specifically for rollup?


If we have externalize flag in resolveExportsOrImports, then technically can we also pass this flag instead of swapping off options.conditions for fetchModule node resolve?

const resolved = tryNodeResolve(url, importer, {
mainFields: ['main'],
conditions: externalConditions,
externalConditions,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, it's only for build? Might not be relevant to this PR specifically but curious what's the idea for options.isBuild check? Is this because Vite crawl imports by ourselves by import analysis resolve (and not resolve for external) during dev, but this is specifically for rollup?

It is only for build. The purpose is to run this part of code:

let resolvedId = id
if (
deepMatch &&
!pkg.data.exports &&
path.extname(id) !== path.extname(resolved.id)
) {
// id date-fns/locale
// resolve.id ...date-fns/esm/locale/index.js
const index = resolved.id.indexOf(id)
if (index > -1) {
resolvedId = resolved.id.slice(index)
debug?.(
`[processResult] ${colors.cyan(id)} -> ${colors.dim(resolvedId)}`,
)
}
}
return { ...resolved, id: resolvedId, external: true }

In dev, this specifier rewrite is not needed because fetchModule handles that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have externalize flag in resolveExportsOrImports, then technically can we also pass this flag instead of swapping off options.conditions for fetchModule node resolve?

No, we cannot do that. Because tryNodeResolve will return a non-absolute id when externalize is true (e.g. react/jsx-runtime).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reference 👍 As I understand, the latter part of processResult where it checks resolved.id is fairly an edge case, so previously using conditions instead of externalConditions wouldn't have made much difference.

I'm still digesting the history of that logic, but will follow up on that on my own.

if (condition === DEV_PROD_CONDITION) {
return options.isProduction ? 'production' : 'development'
}
Expand All @@ -1012,6 +1023,7 @@ function resolveDeepImport(
id: string,
{ setResolvedCache, getResolvedCache, dir, data }: PackageData,
options: InternalResolveOptions,
externalize?: boolean,
): string | undefined {
const cache = getResolvedCache(id, options)
if (cache) {
Expand All @@ -1026,7 +1038,13 @@ function resolveDeepImport(
if (isObject(exportsField) && !Array.isArray(exportsField)) {
// resolve without postfix (see #7098)
const { file, postfix } = splitFileAndPostfix(relativeId)
const exportsId = resolveExportsOrImports(data, file, options, 'exports')
const exportsId = resolveExportsOrImports(
data,
file,
options,
'exports',
externalize,
)
if (exportsId !== undefined) {
relativeId = exportsId + postfix
} else {
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/ssr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface SSROptions {
/**
* Conditions that are used during ssr import (including `ssrLoadModule`) of externalized dependencies.
*
* @default ['node']
* @default ['node', 'module-sync']
*/
externalConditions?: string[]

Expand Down
14 changes: 10 additions & 4 deletions playground/ssr-resolve/__tests__/ssr-resolve.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { execFile } from 'node:child_process'
import { promisify } from 'node:util'
import { expect, test } from 'vitest'
import { isBuild, readFile, testDir } from '~utils'

const execFileAsync = promisify(execFile)

test.runIf(isBuild)('correctly resolve entrypoints', async () => {
const contents = readFile('dist/main.mjs')

Expand All @@ -19,11 +23,13 @@ test.runIf(isBuild)('correctly resolve entrypoints', async () => {
new RegExp(`from ${_}@vitejs/test-deep-import/foo/index.js${_}`),
)

expect(contents).toMatch(
new RegExp(`from ${_}@vitejs/test-deep-import/bar${_}`),
)
// expect(contents).toMatch(
// new RegExp(`from ${_}@vitejs/test-deep-import/utils/bar.js${_}`),
// )

expect(contents).toMatch(new RegExp(`from ${_}@vitejs/test-module-sync${_}`))

await expect(import(`${testDir}/dist/main.mjs`)).resolves.toBeTruthy()
await execFileAsync('node', [`${testDir}/dist/main.mjs`])
})

test.runIf(isBuild)(
Expand Down
6 changes: 4 additions & 2 deletions playground/ssr-resolve/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import fileEntry from '@vitejs/test-entries/file'
// has `exports` key, should resolve to pkg-exports/entry
import pkgExportsEntry from '@vitejs/test-resolve-pkg-exports/entry'
import deepFoo from '@vitejs/test-deep-import/foo'
import deepBar from '@vitejs/test-deep-import/bar'
// import deepBar from '@vitejs/test-deep-import/bar'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented out this test case as it wasn't working.
The output contains @vitejs/test-deep-import/bar as-is, but to make that work in Node, it needs to be rewritten to @vitejs/test-deep-import/utils/bar.js as Node does not read package.json for directory specifiers in ESM.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean this could be a breaking change if someone relied on the non-standard behavior before?

I'm ok with trying this out in a minor, but I think if we're not going to support this case here we can directly remove the test for it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this code was not working without the change in this PR.
I changed the test code to run it in Node for #20409 (comment) and that caused the test to fail.
https://github.com/vitejs/vite/pull/20409/files#diff-859b1585f9b836443382acf98d7530dd7666ca2e0931021e82f745af51e233a3L26-R32

import moduleSync from '@vitejs/test-module-sync'
import { used } from './util'

export default `
entries/dir: ${dirEntry}
entries/file: ${fileEntry}
pkg-exports/entry: ${pkgExportsEntry}
deep-import/foo: ${deepFoo}
deep-import/bar: ${deepBar}
${/* `deep-import/bar: ${deepBar}` */ ''}
module-sync: ${moduleSync}
util: ${used(['[success]'])}
`
5 changes: 3 additions & 2 deletions playground/ssr-resolve/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"debug": "node --inspect-brk ../../packages/vite/bin/vite build"
},
"dependencies": {
"@vitejs/test-deep-import": "file:./deep-import",
"@vitejs/test-entries": "file:./entries",
"@vitejs/test-resolve-pkg-exports": "file:./pkg-exports",
"@vitejs/test-deep-import": "file:./deep-import"
"@vitejs/test-module-sync": "file:./pkg-module-sync",
"@vitejs/test-resolve-pkg-exports": "file:./pkg-exports"
}
}
1 change: 1 addition & 0 deletions playground/ssr-resolve/pkg-module-sync/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'module-sync'
11 changes: 11 additions & 0 deletions playground/ssr-resolve/pkg-module-sync/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@vitejs/test-module-sync",
"type": "module",
"private": true,
"version": "0.0.0",
"exports": {
".": {
"module-sync": "./index.js"
}
}
}
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading