From 76e58a4545d69bc8d554e06c090994568dfe2e22 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 13 Nov 2024 12:01:11 +0900 Subject: [PATCH 1/4] feat: expose default mainFields/conditions --- docs/config/shared-options.md | 4 ++-- docs/config/ssr-options.md | 2 +- docs/guide/migration.md | 4 ++-- packages/vite/src/node/publicUtils.ts | 8 +++++++- playground/resolve/vite.config.js | 4 ++-- playground/ssr-conditions/vite.config.js | 4 ++-- playground/ssr-webworker/vite.config.js | 4 ++-- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md index f375d060e9ace3..8d4c36ff67d90d 100644 --- a/docs/config/shared-options.md +++ b/docs/config/shared-options.md @@ -117,7 +117,7 @@ For SSR builds, deduplication does not work for ESM build outputs configured fro ## resolve.conditions - **Type:** `string[]` -- **Default:** `['module', 'browser', 'development|production']` +- **Default:** `['module', 'browser', 'development|production']` (`defaultClientConditions`) Additional allowed conditions when resolving [Conditional Exports](https://nodejs.org/api/packages.html#packages_conditional_exports) from a package. @@ -147,7 +147,7 @@ Export keys ending with "/" is deprecated by Node and may not work well. Please ## resolve.mainFields - **Type:** `string[]` -- **Default:** `['browser', 'module', 'jsnext:main', 'jsnext']` +- **Default:** `['browser', 'module', 'jsnext:main', 'jsnext']` (`defaultClientMainFields`) List of fields in `package.json` to try when resolving a package's entry point. Note this takes lower precedence than conditional exports resolved from the `exports` field: if an entry point is successfully resolved from `exports`, the main field will be ignored. diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md index c48cb0d0d4dfb0..1836a15ab561e6 100644 --- a/docs/config/ssr-options.md +++ b/docs/config/ssr-options.md @@ -34,7 +34,7 @@ Build target for the SSR server. ## ssr.resolve.conditions - **Type:** `string[]` -- **Default:** `['module', 'node', 'development|production']` (`['module', 'browser', 'development|production']` for `ssr.target === 'webworker'`) +- **Default:** `['module', 'node', 'development|production']` (`defaultServerConditions`) (`['module', 'browser', 'development|production']` (`defaultClientConditions`) for `ssr.target === 'webworker'`) - **Related:** [Resolve Conditions](./shared-options.md#resolve-conditions) These conditions are used in the plugin pipeline, and only affect non-externalized dependencies during the SSR build. Use `ssr.resolve.externalConditions` to affect externalized imports. diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 771aa2e4b75e5a..9ab77d6a1c9c89 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -24,10 +24,10 @@ The conditions that are no longer added internally for - `resolve.conditions` are `['module', 'browser', 'development|production']` - `ssr.resolve.conditions` are `['module', 'node', 'development|production']` -The default values for those options are updated to the corresponding values and `ssr.resolve.conditions` no longer uses `resolve.conditions` as the default value. Note that `development|production` is a special variable that is replaced with `production` or `development` depending on the value of `process.env.NODE_ENV`. +The default values for those options are updated to the corresponding values and `ssr.resolve.conditions` no longer uses `resolve.conditions` as the default value. Note that `development|production` is a special variable that is replaced with `production` or `development` depending on the value of `process.env.NODE_ENV`. These default values are exported from `vite` as `defaultClientConditions` and `defaultServerConditions`. If you specified a custom value for `resolve.conditions` or `ssr.resolve.conditions`, you need to update it to include the new conditions. -For example, if you previously specified `['custom']` for `resolve.conditions`, you need to specify `['custom', 'module', 'browser', 'development|production']` instead. +For example, if you previously specified `['custom']` for `resolve.conditions`, you need to specify `['custom', ...defaultClientConditions]` instead. ### JSON stringify diff --git a/packages/vite/src/node/publicUtils.ts b/packages/vite/src/node/publicUtils.ts index a9cad7a5106db6..872ed41eb28334 100644 --- a/packages/vite/src/node/publicUtils.ts +++ b/packages/vite/src/node/publicUtils.ts @@ -3,7 +3,13 @@ * This file will be bundled to ESM and CJS and redirected by ../index.cjs * Please control the side-effects by checking the ./dist/node-cjs/publicUtils.cjs bundle */ -export { VERSION as version } from './constants' +export { + VERSION as version, + DEFAULT_CLIENT_CONDITIONS as defaultClientConditions, + DEFAULT_CLIENT_MAIN_FIELDS as defaultClientMainFields, + DEFAULT_SERVER_CONDITIONS as defaultServerConditions, + DEFAULT_SERVER_MAIN_FIELDS as defaultServerMainFields, +} from './constants' export { version as esbuildVersion } from 'esbuild' export { splitVendorChunkPlugin, diff --git a/playground/resolve/vite.config.js b/playground/resolve/vite.config.js index d12eb2283c3333..9216e5db62e818 100644 --- a/playground/resolve/vite.config.js +++ b/playground/resolve/vite.config.js @@ -1,5 +1,5 @@ import path from 'node:path' -import { defineConfig, normalizePath } from 'vite' +import { defaultClientConditions, defineConfig, normalizePath } from 'vite' import { a } from './config-dep.cjs' const virtualFile = '@virtual-file' @@ -32,7 +32,7 @@ export default defineConfig({ resolve: { extensions: ['.mjs', '.js', '.es', '.ts'], mainFields: ['browser', 'custom', 'module'], - conditions: ['module', 'browser', 'development|production', 'custom'], + conditions: [...defaultClientConditions, 'custom'], }, define: { VITE_CONFIG_DEP_TEST: a, diff --git a/playground/ssr-conditions/vite.config.js b/playground/ssr-conditions/vite.config.js index e59e1c2a612556..78eb0df4d35fe1 100644 --- a/playground/ssr-conditions/vite.config.js +++ b/playground/ssr-conditions/vite.config.js @@ -1,11 +1,11 @@ -import { defineConfig } from 'vite' +import { defaultServerConditions, defineConfig } from 'vite' export default defineConfig({ ssr: { external: ['@vitejs/test-ssr-conditions-external'], noExternal: ['@vitejs/test-ssr-conditions-no-external'], resolve: { - conditions: ['module', 'node', 'development|production', 'react-server'], + conditions: [...defaultServerConditions, 'react-server'], externalConditions: ['node', 'workerd', 'react-server'], }, }, diff --git a/playground/ssr-webworker/vite.config.js b/playground/ssr-webworker/vite.config.js index 223b79e5f8ba4c..a2542d7d422c07 100644 --- a/playground/ssr-webworker/vite.config.js +++ b/playground/ssr-webworker/vite.config.js @@ -1,4 +1,4 @@ -import { defineConfig } from 'vite' +import { defaultClientConditions, defineConfig } from 'vite' export default defineConfig({ build: { @@ -14,7 +14,7 @@ export default defineConfig({ // in the runtime, and so we can externalize it when bundling. external: ['node:assert'], resolve: { - conditions: ['module', 'browser', 'development|production', 'worker'], + conditions: [...defaultClientConditions, 'worker'], }, }, plugins: [ From 1592104b53beff27c4f26de7a0d296cb991c01e7 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 13 Nov 2024 12:54:20 +0900 Subject: [PATCH 2/4] chore: freeze exposed arrays --- packages/vite/src/node/constants.ts | 14 +++++++------- packages/vite/src/node/plugins/resolve.ts | 6 +++--- packages/vite/src/node/ssr/index.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 0b10a990350817..b251eddb6f03c9 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -45,9 +45,9 @@ const DEFAULT_MAIN_FIELDS = [ 'jsnext:main', // moment still uses this... 'jsnext', ] -export const DEFAULT_CLIENT_MAIN_FIELDS = DEFAULT_MAIN_FIELDS -export const DEFAULT_SERVER_MAIN_FIELDS = DEFAULT_MAIN_FIELDS.filter( - (f) => f !== 'browser', +export const DEFAULT_CLIENT_MAIN_FIELDS = Object.freeze(DEFAULT_MAIN_FIELDS) +export const DEFAULT_SERVER_MAIN_FIELDS = Object.freeze( + DEFAULT_MAIN_FIELDS.filter((f) => f !== 'browser'), ) /** @@ -57,11 +57,11 @@ export const DEFAULT_SERVER_MAIN_FIELDS = DEFAULT_MAIN_FIELDS.filter( export const DEV_PROD_CONDITION = `development|production` as const const DEFAULT_CONDITIONS = ['module', 'browser', 'node', DEV_PROD_CONDITION] -export const DEFAULT_CLIENT_CONDITIONS = DEFAULT_CONDITIONS.filter( - (c) => c !== 'node', +export const DEFAULT_CLIENT_CONDITIONS = Object.freeze( + DEFAULT_CONDITIONS.filter((c) => c !== 'node'), ) -export const DEFAULT_SERVER_CONDITIONS = DEFAULT_CONDITIONS.filter( - (c) => c !== 'browser', +export const DEFAULT_SERVER_CONDITIONS = Object.freeze( + DEFAULT_CONDITIONS.filter((c) => c !== 'browser'), ) // Baseline support browserslist diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index bc11a557f38a0c..92ab732c164a27 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -75,9 +75,9 @@ export interface EnvironmentResolveOptions { /** * @default ['browser', 'module', 'jsnext:main', 'jsnext'] */ - mainFields?: string[] - conditions?: string[] - externalConditions?: string[] + mainFields?: readonly string[] + conditions?: readonly string[] + externalConditions?: readonly string[] /** * @default ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'] */ diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts index 45901f069b565c..b617d887223ab3 100644 --- a/packages/vite/src/node/ssr/index.ts +++ b/packages/vite/src/node/ssr/index.ts @@ -35,14 +35,14 @@ export interface SSROptions { * * @default rootConfig.resolve.conditions */ - conditions?: string[] + conditions?: readonly string[] /** * Conditions that are used during ssr import (including `ssrLoadModule`) of externalized dependencies. * * @default [] */ - externalConditions?: string[] + externalConditions?: readonly string[] } } From bda9b754fe84c6b014860640fcb9e7f8fc846797 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:35:23 +0900 Subject: [PATCH 3/4] chore: use DeepWriteable --- packages/vite/src/node/plugins/resolve.ts | 6 +++--- packages/vite/src/node/ssr/index.ts | 4 ++-- packages/vite/src/node/utils.ts | 23 ++++++++++++++++------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 92ab732c164a27..bc11a557f38a0c 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -75,9 +75,9 @@ export interface EnvironmentResolveOptions { /** * @default ['browser', 'module', 'jsnext:main', 'jsnext'] */ - mainFields?: readonly string[] - conditions?: readonly string[] - externalConditions?: readonly string[] + mainFields?: string[] + conditions?: string[] + externalConditions?: string[] /** * @default ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'] */ diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts index b617d887223ab3..45901f069b565c 100644 --- a/packages/vite/src/node/ssr/index.ts +++ b/packages/vite/src/node/ssr/index.ts @@ -35,14 +35,14 @@ export interface SSROptions { * * @default rootConfig.resolve.conditions */ - conditions?: readonly string[] + conditions?: string[] /** * Conditions that are used during ssr import (including `ssrLoadModule`) of externalized dependencies. * * @default [] */ - externalConditions?: readonly string[] + externalConditions?: string[] } } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 60b78f869cfffa..b47d0f807b7621 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1070,27 +1070,36 @@ function backwardCompatibleWorkerPlugins(plugins: any) { return [] } -function deepClone(value: T): T { +type DeepWriteable = + T extends ReadonlyArray + ? { -readonly [P in keyof T]: DeepWriteable } + : T extends RegExp + ? RegExp + : T[keyof T] extends Function + ? T + : { -readonly [P in keyof T]: DeepWriteable } + +function deepClone(value: T): DeepWriteable { if (Array.isArray(value)) { - return value.map((v) => deepClone(v)) as T + return value.map((v) => deepClone(v)) as DeepWriteable } if (isObject(value)) { const cloned: Record = {} for (const key in value) { cloned[key] = deepClone(value[key]) } - return cloned as T + return cloned as DeepWriteable } if (typeof value === 'function') { - return value as T + return value as DeepWriteable } if (value instanceof RegExp) { - return structuredClone(value) + return structuredClone(value) as DeepWriteable } if (typeof value === 'object' && value != null) { throw new Error('Cannot deep clone non-plain object') } - return value + return value as DeepWriteable } type MaybeFallback = undefined extends V ? Exclude | D : V @@ -1146,7 +1155,7 @@ function mergeWithDefaultsRecursively< export function mergeWithDefaults< D extends Record, V extends Record, ->(defaults: D, values: V): MergeWithDefaultsResult { +>(defaults: D, values: V): MergeWithDefaultsResult, V> { // NOTE: we need to clone the value here to avoid mutating the defaults const clonedDefaults = deepClone(defaults) return mergeWithDefaultsRecursively(clonedDefaults, values) From 7e684db27d1e6c9cf74f3bf64d80454df2aa5472 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:38:57 +0900 Subject: [PATCH 4/4] chore: it's DeepWritable --- packages/vite/src/node/utils.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index b47d0f807b7621..0a9d1e37e3f31b 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1070,36 +1070,36 @@ function backwardCompatibleWorkerPlugins(plugins: any) { return [] } -type DeepWriteable = +type DeepWritable = T extends ReadonlyArray - ? { -readonly [P in keyof T]: DeepWriteable } + ? { -readonly [P in keyof T]: DeepWritable } : T extends RegExp ? RegExp : T[keyof T] extends Function ? T - : { -readonly [P in keyof T]: DeepWriteable } + : { -readonly [P in keyof T]: DeepWritable } -function deepClone(value: T): DeepWriteable { +function deepClone(value: T): DeepWritable { if (Array.isArray(value)) { - return value.map((v) => deepClone(v)) as DeepWriteable + return value.map((v) => deepClone(v)) as DeepWritable } if (isObject(value)) { const cloned: Record = {} for (const key in value) { cloned[key] = deepClone(value[key]) } - return cloned as DeepWriteable + return cloned as DeepWritable } if (typeof value === 'function') { - return value as DeepWriteable + return value as DeepWritable } if (value instanceof RegExp) { - return structuredClone(value) as DeepWriteable + return structuredClone(value) as DeepWritable } if (typeof value === 'object' && value != null) { throw new Error('Cannot deep clone non-plain object') } - return value as DeepWriteable + return value as DeepWritable } type MaybeFallback = undefined extends V ? Exclude | D : V @@ -1155,7 +1155,7 @@ function mergeWithDefaultsRecursively< export function mergeWithDefaults< D extends Record, V extends Record, ->(defaults: D, values: V): MergeWithDefaultsResult, V> { +>(defaults: D, values: V): MergeWithDefaultsResult, V> { // NOTE: we need to clone the value here to avoid mutating the defaults const clonedDefaults = deepClone(defaults) return mergeWithDefaultsRecursively(clonedDefaults, values)