diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md index a51350d05c462b..0ebddfd988bb04 100644 --- a/docs/config/shared-options.md +++ b/docs/config/shared-options.md @@ -158,6 +158,16 @@ Export keys ending with "/" is deprecated by Node and may not work well. Please 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. +## resolve.browserField + +- **Type:** `boolean` +- **Default:** `true` +- **Deprecated** + +Whether to enable resolving to `browser` field. + +In future, `resolve.mainFields`'s default value will be `['browser', 'module', 'jsnext:main', 'jsnext']` and this option will be removed. + ## resolve.extensions - **Type:** `string[]` diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 829c47fee07abf..46ee9476677c2d 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -43,6 +43,8 @@ import { CLIENT_ENTRY, DEFAULT_ASSETS_RE, DEFAULT_CONFIG_FILES, + DEFAULT_EXTENSIONS, + DEFAULT_MAIN_FIELDS, ENV_ENTRY } from './constants' import type { InternalResolveOptions, ResolveOptions } from './plugins/resolve' @@ -321,7 +323,7 @@ export type ResolvedConfig = Readonly< mainConfig: ResolvedConfig | null isProduction: boolean env: Record - resolve: ResolveOptions & { + resolve: Required & { alias: Alias[] } plugins: readonly Plugin[] @@ -474,7 +476,12 @@ export async function resolveConfig( ) const resolveOptions: ResolvedConfig['resolve'] = { - ...config.resolve, + mainFields: config.resolve?.mainFields ?? DEFAULT_MAIN_FIELDS, + browserField: config.resolve?.browserField ?? true, + conditions: config.resolve?.conditions ?? [], + extensions: config.resolve?.extensions ?? DEFAULT_EXTENSIONS, + dedupe: config.resolve?.dedupe ?? [], + preserveSymlinks: config.resolve?.preserveSymlinks ?? false, alias: resolvedAlias } @@ -581,8 +588,8 @@ export async function resolveConfig( const server = resolveServerOptions(resolvedRoot, config.server, logger) const ssr = resolveSSROptions( config.ssr, - config.legacy?.buildSsrCjsExternalHeuristics, - config.resolve?.preserveSymlinks + resolveOptions.preserveSymlinks, + config.legacy?.buildSsrCjsExternalHeuristics ) const middlewareMode = config?.server?.middlewareMode @@ -649,7 +656,7 @@ export async function resolveConfig( disabled: 'build', ...optimizeDeps, esbuildOptions: { - preserveSymlinks: config.resolve?.preserveSymlinks, + preserveSymlinks: resolveOptions.preserveSymlinks, ...optimizeDeps.esbuildOptions } }, diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 97b032b2833e11..01a7e478fa4998 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -62,13 +62,18 @@ const debug = createDebugger('vite:resolve-details', { export interface ResolveOptions { mainFields?: string[] + /** + * @deprecated In future, `mainFields` should be used instead. + * @default true + */ + browserField?: boolean conditions?: string[] extensions?: string[] dedupe?: string[] preserveSymlinks?: boolean } -export interface InternalResolveOptions extends ResolveOptions { +export interface InternalResolveOptions extends Required { root: string isBuild: boolean isProduction: boolean @@ -84,7 +89,6 @@ export interface InternalResolveOptions extends ResolveOptions { tryPrefix?: string skipPackageJson?: boolean preferRelative?: boolean - preserveSymlinks?: boolean isRequire?: boolean // #3040 // when the importer is a ts module, @@ -237,6 +241,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { if ( targetWeb && + options.browserField && (res = tryResolveBrowserMapping(fsPath, importer, options, true)) ) { return res @@ -307,6 +312,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { if ( targetWeb && + options.browserField && (res = tryResolveBrowserMapping( id, importer, @@ -450,7 +456,7 @@ function tryFsResolve( return res } - for (const ext of options.extensions || DEFAULT_EXTENSIONS) { + for (const ext of options.extensions) { if ( postfix && (res = tryResolveFile( @@ -885,7 +891,11 @@ export function resolvePackageEntry( // This is because .mjs files can technically import .cjs files which would // make them invalid for pure ESM environments - so if other module/browser // fields are present, prioritize those instead. - if (targetWeb && (!entryPoint || entryPoint.endsWith('.mjs'))) { + if ( + targetWeb && + options.browserField && + (!entryPoint || entryPoint.endsWith('.mjs')) + ) { // check browser field // https://github.com/defunctzombie/package-browser-field-spec const browserEntry = @@ -896,6 +906,7 @@ export function resolvePackageEntry( // check if the package also has a "module" field. if ( !options.isRequire && + options.mainFields.includes('module') && typeof data.module === 'string' && data.module !== browserEntry ) { @@ -926,7 +937,8 @@ export function resolvePackageEntry( } if (!entryPoint || entryPoint.endsWith('.mjs')) { - for (const field of options.mainFields || DEFAULT_MAIN_FIELDS) { + for (const field of options.mainFields) { + if (field === 'browser') continue // already checked above if (typeof data[field] === 'string') { entryPoint = data[field] break @@ -944,8 +956,8 @@ export function resolvePackageEntry( for (let entry of entryPoints) { // make sure we don't get scripts when looking for sass if ( - options.mainFields?.[0] === 'sass' && - !options.extensions?.includes(path.extname(entry)) + options.mainFields[0] === 'sass' && + !options.extensions.includes(path.extname(entry)) ) { entry = '' options.skipPackageJson = true @@ -953,7 +965,7 @@ export function resolvePackageEntry( // resolve object browser field in package.json const { browser: browserField } = data - if (targetWeb && isObject(browserField)) { + if (targetWeb && options.browserField && isObject(browserField)) { entry = mapWithBrowserField(entry, browserField) || entry } @@ -994,7 +1006,7 @@ function resolveExports( if (!options.isRequire) { conditions.push('module') } - if (options.conditions) { + if (options.conditions.length > 0) { conditions.push(...options.conditions) } @@ -1046,7 +1058,7 @@ function resolveDeepImport( `${path.join(dir, 'package.json')}.` ) } - } else if (targetWeb && isObject(browserField)) { + } else if (targetWeb && options.browserField && isObject(browserField)) { // resolve without postfix (see #7098) const { file, postfix } = splitFileAndPostfix(relativeId) const mapped = mapWithBrowserField(file, browserField) diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts index d1173b211ff836..e02f9b48e6b6c7 100644 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ b/packages/vite/src/node/plugins/ssrRequireHook.ts @@ -13,7 +13,7 @@ export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null { if ( config.command !== 'build' || !config.build.ssr || - !config.resolve.dedupe?.length || + !config.resolve.dedupe.length || config.ssr?.noExternal === true || config.ssr?.format !== 'cjs' || isBuildOutputEsm(config) diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts index 7d2e4724f98b44..d23e78b18cae5f 100644 --- a/packages/vite/src/node/ssr/index.ts +++ b/packages/vite/src/node/ssr/index.ts @@ -41,8 +41,8 @@ export interface ResolvedSSROptions extends SSROptions { export function resolveSSROptions( ssr: SSROptions | undefined, - buildSsrCjsExternalHeuristics?: boolean, - preserveSymlinks?: boolean + preserveSymlinks: boolean, + buildSsrCjsExternalHeuristics?: boolean ): ResolvedSSROptions { ssr ??= {} const optimizeDeps = ssr.optimizeDeps ?? {} diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index d73d17c1d7c3c8..7c48c84db48743 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { createRequire } from 'node:module' -import type { InternalResolveOptions } from '../plugins/resolve' +import type { InternalResolveOptions, ResolveOptions } from '../plugins/resolve' import { tryNodeResolve } from '../plugins/resolve' import { bareImportRE, @@ -53,7 +53,7 @@ export function cjsSsrResolveExternals( cjsSsrCollectExternals( config.root, - config.resolve.preserveSymlinks, + config.resolve, ssrExternals, seen, config.logger @@ -116,8 +116,8 @@ export function createIsConfiguredAsSsrExternal( createFilter(undefined, noExternal, { resolve: false }) const resolveOptions: InternalResolveOptions = { + ...config.resolve, root, - preserveSymlinks: config.resolve.preserveSymlinks, isProduction: false, isBuild: true } @@ -211,7 +211,7 @@ function createIsSsrExternal( // is used reverting to the Vite 2.9 SSR externalization heuristics function cjsSsrCollectExternals( root: string, - preserveSymlinks: boolean | undefined, + resolveOptions: Required, ssrExternals: Set, seen: Set, logger: Logger @@ -227,9 +227,9 @@ function cjsSsrCollectExternals( ...rootPkg.dependencies } - const resolveOptions: InternalResolveOptions = { + const internalResolveOptions: InternalResolveOptions = { + ...resolveOptions, root, - preserveSymlinks, isProduction: false, isBuild: true } @@ -247,7 +247,7 @@ function cjsSsrCollectExternals( esmEntry = tryNodeResolve( id, undefined, - resolveOptions, + internalResolveOptions, true, // we set `targetWeb` to `true` to get the ESM entry undefined, true @@ -314,13 +314,7 @@ function cjsSsrCollectExternals( } for (const depRoot of depsToTrace) { - cjsSsrCollectExternals( - depRoot, - preserveSymlinks, - ssrExternals, - seen, - logger - ) + cjsSsrCollectExternals(depRoot, resolveOptions, ssrExternals, seen, logger) } } diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index 8f61125c134d8c..6daf1a6123c804 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -119,13 +119,15 @@ async function instantiateModule( // CommonJS modules are preferred. We want to avoid ESM->ESM imports // whenever possible, because `hookNodeResolve` can't intercept them. const resolveOptions: InternalResolveOptions = { - dedupe, + mainFields: ['main'], + browserField: true, + conditions: [], extensions: ['.js', '.cjs', '.json'], + dedupe, + preserveSymlinks, isBuild: true, isProduction, isRequire: true, - mainFields: ['main'], - preserveSymlinks, root }