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(resolve)!: allow removing conditions #18395

Merged
merged 22 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 docs/config/shared-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ For SSR builds, deduplication does not work for ESM build outputs configured fro
## resolve.conditions

- **Type:** `string[]`
- **Default:** `['module', 'browser', 'node', 'production', 'development']`

Additional allowed conditions when resolving [Conditional Exports](https://nodejs.org/api/packages.html#packages_conditional_exports) from a package.

Expand All @@ -135,7 +136,9 @@ A package with conditional exports may have the following `exports` field in its

Here, `import` and `require` are "conditions". Conditions can be nested and should be specified from most specific to least specific.

Vite has a list of "allowed conditions" and will match the first condition that is in the allowed list. The default allowed conditions are: `import`, `module`, `browser`, `default`, and `production/development` based on current mode. The `resolve.conditions` config option allows specifying additional allowed conditions.
Some of the default conditions (`production`, `development`, `browser`, `node`) are only applied when the requirements are met. For example, `production` is only applied when `process.env.NODE_ENV === 'production'`, and `browser` is only applied when the environment is `webCompatible`. The `resolve.conditions` config option allows specifying additional allowed conditions and those conditions will be applied unconditionally.
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved

Note that `import`, `require`, `default` conditions are always applied if the requirements are met.

:::warning Resolving subpath exports
Export keys ending with "/" is deprecated by Node and may not work well. Please contact the package author to use [`*` subpath patterns](https://nodejs.org/api/packages.html#package-entry-points) instead.
Expand Down
2 changes: 1 addition & 1 deletion docs/config/ssr-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ These conditions are used in the plugin pipeline, and only affect non-externaliz
## ssr.resolve.externalConditions

- **Type:** `string[]`
- **Default:** `[]`
- **Default:** `['node']`

Conditions that are used during ssr import (including `ssrLoadModule`) of externalized dependencies.
10 changes: 6 additions & 4 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { withTrailingSlash } from '../shared/utils'
import {
CLIENT_ENTRY,
DEFAULT_ASSETS_RE,
DEFAULT_CONDITIONS,
DEFAULT_CONFIG_FILES,
DEFAULT_EXTENSIONS,
DEFAULT_EXTERNAL_CONDITIONS,
DEFAULT_MAIN_FIELDS,
ENV_ENTRY,
FS_PREFIX,
Expand Down Expand Up @@ -739,8 +741,9 @@ function resolveEnvironmentResolveOptions(
): ResolvedAllResolveOptions {
const resolvedResolve: ResolvedAllResolveOptions = {
mainFields: resolve?.mainFields ?? DEFAULT_MAIN_FIELDS,
conditions: resolve?.conditions ?? [],
externalConditions: resolve?.externalConditions ?? [],
conditions: resolve?.conditions ?? DEFAULT_CONDITIONS,
externalConditions:
resolve?.externalConditions ?? DEFAULT_EXTERNAL_CONDITIONS,
external: resolve?.external ?? [],
noExternal: resolve?.noExternal ?? [],
extensions: resolve?.extensions ?? DEFAULT_EXTENSIONS,
Expand Down Expand Up @@ -1570,11 +1573,10 @@ async function bundleConfigFile(
preferRelative: false,
tryIndex: true,
mainFields: [],
conditions: [],
conditions: ['node'],
externalConditions: [],
external: [],
noExternal: [],
overrideConditions: ['node'],
dedupe: [],
extensions: DEFAULT_EXTENSIONS,
preserveSymlinks: false,
Expand Down
10 changes: 10 additions & 0 deletions packages/vite/src/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ export const DEFAULT_MAIN_FIELDS = [
'jsnext',
]

export const DEFAULT_CONDITIONS = [
'module',
'browser',
'node',
'production',
'development',
]

export const DEFAULT_EXTERNAL_CONDITIONS = ['node']

// Baseline support browserslist
// "defaults and supports es6-module and supports es6-module-dynamic-import"
// Higher browser versions may be needed for extra features.
Expand Down
12 changes: 4 additions & 8 deletions packages/vite/src/node/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
tryStatSync,
} from './utils'
import type { Plugin } from './plugin'
import type { InternalResolveOptionsWithOverrideConditions } from './plugins/resolve'
import type { InternalResolveOptions } from './plugins/resolve'

let pnp: typeof import('pnpapi') | undefined
if (process.versions.pnp) {
Expand All @@ -27,11 +27,11 @@ export interface PackageData {
setResolvedCache: (
key: string,
entry: string,
options: InternalResolveOptionsWithOverrideConditions,
options: InternalResolveOptions,
) => void
getResolvedCache: (
key: string,
options: InternalResolveOptionsWithOverrideConditions,
options: InternalResolveOptions,
) => string | undefined
data: {
[field: string]: any
Expand Down Expand Up @@ -223,18 +223,14 @@ export function loadPackageData(pkgPath: string): PackageData {
return pkg
}

function getResolveCacheKey(
key: string,
options: InternalResolveOptionsWithOverrideConditions,
) {
function getResolveCacheKey(key: string, options: InternalResolveOptions) {
// cache key needs to include options which affect
// `resolvePackageEntry` or `resolveDeepImport`
return [
key,
options.webCompatible ? '1' : '0',
options.isRequire ? '1' : '0',
options.conditions.join('_'),
options.overrideConditions?.join('_') || '',
options.extensions.join('_'),
options.mainFields.join('_'),
].join('|')
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ function createCSSResolvers(config: ResolvedConfig): CSSAtImportResolvers {
return (cssResolve ??= createBackCompatIdResolver(config, {
extensions: ['.css'],
mainFields: ['style'],
conditions: ['style'],
conditions: ['style', 'production', 'development'],
tryIndex: false,
preferRelative: true,
}))
Expand All @@ -1090,7 +1090,7 @@ function createCSSResolvers(config: ResolvedConfig): CSSAtImportResolvers {
const resolver = createBackCompatIdResolver(config, {
extensions: ['.scss', '.sass', '.css'],
mainFields: ['sass', 'style'],
conditions: ['sass', 'style'],
conditions: ['sass', 'style', 'production', 'development'],
tryIndex: true,
tryPrefix: '_',
preferRelative: true,
Expand All @@ -1109,7 +1109,7 @@ function createCSSResolvers(config: ResolvedConfig): CSSAtImportResolvers {
return (lessResolve ??= createBackCompatIdResolver(config, {
extensions: ['.less', '.css'],
mainFields: ['less', 'style'],
conditions: ['less', 'style'],
conditions: ['less', 'style', 'production', 'development'],
tryIndex: false,
preferRelative: true,
}))
Expand Down
54 changes: 21 additions & 33 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,18 +736,10 @@ function tryCleanFsResolve(
}
}

export type InternalResolveOptionsWithOverrideConditions =
InternalResolveOptions & {
/**
* @internal
*/
overrideConditions?: string[]
}

export function tryNodeResolve(
id: string,
importer: string | null | undefined,
options: InternalResolveOptionsWithOverrideConditions,
options: InternalResolveOptions,
depsOptimizer?: DepsOptimizer,
ssr: boolean = false,
externalize?: boolean,
Expand Down Expand Up @@ -1117,35 +1109,31 @@ function packageEntryFailure(id: string, details?: string) {
function resolveExportsOrImports(
pkg: PackageData['data'],
key: string,
options: InternalResolveOptionsWithOverrideConditions,
options: InternalResolveOptions,
type: 'imports' | 'exports',
) {
const additionalConditions = new Set(
options.overrideConditions || [
'production',
'development',
'module',
...options.conditions,
],
const conditions = [...options.conditions, 'require', 'import'].filter(
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 didn't give users a way to remove require / import as I didn't understand how that would work.

(condition) => {
switch (condition) {
case 'production':
return options.isProduction
case 'development':
return !options.isProduction
case 'require':
return options.isRequire
case 'import':
return !options.isRequire
case 'node':
return !options.webCompatible
case 'browser':
return options.webCompatible
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'm using webCompatible here for now. But probably we should do it in the env side. I left this for now to do that when removing the webCompatible flag. I think it would be easier to understand the diff later if it's done at the same time with the browser field.

Copy link
Member Author

Choose a reason for hiding this comment

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

Update: I removed webCompatible in this PR (edcf8f3)

}
return true
},
)

const conditions = [...additionalConditions].filter((condition) => {
switch (condition) {
case 'production':
return options.isProduction
case 'development':
return !options.isProduction
}
return true
})

const fn = type === 'imports' ? imports : exports
const result = fn(pkg, key, {
browser: options.webCompatible && !additionalConditions.has('node'),
require: options.isRequire && !additionalConditions.has('import'),
conditions,
})

const result = fn(pkg, key, { conditions, unsafe: true })
return result ? result[0] : undefined
}

Expand Down
7 changes: 1 addition & 6 deletions packages/vite/src/node/ssr/fetchModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,10 @@ export async function fetchModule(
importer,
{
mainFields: ['main'],
conditions: [],
conditions: [...externalConditions, 'production', 'development'],
externalConditions,
external: [],
noExternal: [],
overrideConditions: [
...externalConditions,
'production',
'development',
],
extensions: ['.js', '.cjs', '.json'],
dedupe,
preserveSymlinks,
Expand Down
9 changes: 8 additions & 1 deletion playground/resolve/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ export default defineConfig({
resolve: {
extensions: ['.mjs', '.js', '.es', '.ts'],
mainFields: ['browser', 'custom', 'module'],
conditions: ['custom'],
conditions: [
'module',
'browser',
'node',
'production',
'development',
'custom',
],
},
define: {
VITE_CONFIG_DEP_TEST: a,
Expand Down
10 changes: 8 additions & 2 deletions playground/ssr-conditions/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ export default defineConfig({
external: ['@vitejs/test-ssr-conditions-external'],
noExternal: ['@vitejs/test-ssr-conditions-no-external'],
resolve: {
conditions: ['react-server'],
externalConditions: ['workerd', 'react-server'],
conditions: [
'module',
'node',
'production',
'development',
'react-server',
],
externalConditions: ['node', 'workerd', 'react-server'],
},
},
})
9 changes: 8 additions & 1 deletion playground/ssr-webworker/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ export default defineConfig({
},
resolve: {
dedupe: ['react'],
conditions: ['worker'],
conditions: [
'module',
'browser',
'node',
'production',
'development',
'worker',
],
},
ssr: {
target: 'webworker',
Expand Down