diff --git a/packages/vite/package.json b/packages/vite/package.json index 4cc39e7f156009..dd8dd089cd1267 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -110,7 +110,7 @@ "postcss-import": "^15.0.0", "postcss-load-config": "^4.0.1", "postcss-modules": "^5.0.0", - "resolve.exports": "^1.1.0", + "resolve.exports": "npm:@alloc/resolve.exports@^1.1.0", "sirv": "^2.0.2", "source-map-js": "^1.0.2", "source-map-support": "^0.5.21", diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e90bdd7150bced..23f4b8c40371e4 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -48,11 +48,7 @@ import { DEFAULT_MAIN_FIELDS, ENV_ENTRY } from './constants' -import type { - InternalResolveOptions, - InternalResolveOptionsWithOverrideConditions, - ResolveOptions -} from './plugins/resolve' +import type { InternalResolveOptions, ResolveOptions } from './plugins/resolve' import { resolvePlugin, tryNodeResolve } from './plugins/resolve' import type { LogLevel, Logger } from './logger' import { createLogger } from './logger' @@ -958,7 +954,7 @@ async function bundleConfigFile( { name: 'externalize-deps', setup(build) { - const options: InternalResolveOptionsWithOverrideConditions = { + const options: InternalResolveOptions = { root: path.dirname(fileName), isBuild: true, isProduction: true, @@ -968,7 +964,7 @@ async function bundleConfigFile( mainFields: [], browserField: false, conditions: [], - overrideConditions: ['node'], + overrideConditions: ['node', 'require'], dedupe: [], extensions: DEFAULT_EXTENSIONS, preserveSymlinks: false diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 1c1c80f573e84d..5cabc36c8d69d4 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -2,7 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import colors from 'picocolors' import type { PartialResolvedId } from 'rollup' -import { resolve as _resolveExports } from 'resolve.exports' +import { resolveExports } from 'resolve.exports' import { hasESMSyntax } from 'mlly' import type { Plugin } from '../plugin' import { @@ -107,6 +107,7 @@ export interface InternalResolveOptions extends Required { shouldExternalize?: (id: string) => boolean | undefined // Check this resolve is called from `hookNodeResolve` in SSR isHookNodeResolve?: boolean + overrideConditions?: string[] } export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { @@ -579,21 +580,12 @@ function tryResolveFile( } } -export type InternalResolveOptionsWithOverrideConditions = - InternalResolveOptions & { - /** - * @deprecated In future, `conditions` will work like this. - * @internal - */ - overrideConditions?: string[] - } - export const idToPkgMap = new Map() export function tryNodeResolve( id: string, importer: string | null | undefined, - options: InternalResolveOptionsWithOverrideConditions, + options: InternalResolveOptions, targetWeb: boolean, depsOptimizer?: DepsOptimizer, ssr?: boolean, @@ -923,29 +915,29 @@ export function resolvePackageEntry( return cached } try { - let entryPoint: string | undefined | void + let entryPoints: string[] = [] - // resolve exports field with highest priority - // using https://github.com/lukeed/resolve.exports + // the exports field takes highest priority as described in + // https://nodejs.org/api/packages.html#package-entry-points if (data.exports) { - entryPoint = resolveExports(data, '.', options, targetWeb) - } - - // if exports resolved to .mjs, still resolve other fields. - // 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 && - options.browserField && - (!entryPoint || entryPoint.endsWith('.mjs')) - ) { + entryPoints = resolveExports( + data, + '.', + options, + getInlineConditions(options, targetWeb), + options.overrideConditions + ) + if (!entryPoints.length) { + packageEntryFailure(id) + } + } else if (targetWeb && options.browserField) { // check browser field // https://github.com/defunctzombie/package-browser-field-spec const browserEntry = typeof data.browser === 'string' ? data.browser : isObject(data.browser) && data.browser['.'] + if (browserEntry) { // check if the package also has a "module" field. if ( @@ -968,34 +960,34 @@ export function resolvePackageEntry( const content = fs.readFileSync(resolvedBrowserEntry, 'utf-8') if (hasESMSyntax(content)) { // likely ESM, prefer browser - entryPoint = browserEntry + entryPoints[0] = browserEntry } else { // non-ESM, UMD or IIFE or CJS(!!! e.g. firebase 7.x), prefer module - entryPoint = data.module + entryPoints[0] = data.module } } } else { - entryPoint = browserEntry + entryPoints[0] = browserEntry } } } - if (!entryPoint || entryPoint.endsWith('.mjs')) { + if (!entryPoints[0]) { for (const field of options.mainFields) { if (field === 'browser') continue // already checked above if (typeof data[field] === 'string') { - entryPoint = data[field] + entryPoints[0] = data[field] break } } + entryPoints[0] ||= data.main } - entryPoint ||= data.main // try default entry when entry is not define // https://nodejs.org/api/modules.html#all-together - const entryPoints = entryPoint - ? [entryPoint] - : ['index.js', 'index.json', 'index.node'] + if (!entryPoints[0]) { + entryPoints = ['index.js', 'index.json', 'index.node'] + } for (let entry of entryPoints) { // make sure we don't get scripts when looking for sass @@ -1040,52 +1032,41 @@ function packageEntryFailure(id: string, details?: string) { ) } -const conditionalConditions = new Set(['production', 'development', 'module']) - -function resolveExports( - pkg: PackageData['data'], - key: string, - options: InternalResolveOptionsWithOverrideConditions, +/** + * This generates conditions that aren't inferred by `resolveExports` + * from the `options` object. + */ +function getInlineConditions( + options: InternalResolveOptions, targetWeb: boolean ) { - const overrideConditions = options.overrideConditions - ? new Set(options.overrideConditions) - : undefined + const inlineConditions: string[] = [] - const conditions = [] - if ( - (!overrideConditions || overrideConditions.has('production')) && - options.isProduction - ) { - conditions.push('production') - } - if ( - (!overrideConditions || overrideConditions.has('development')) && - !options.isProduction - ) { - conditions.push('development') - } - if ( - (!overrideConditions || overrideConditions.has('module')) && - !options.isRequire - ) { - conditions.push('module') + const conditions: readonly string[] = + options.overrideConditions || options.conditions + + if (targetWeb) { + if (!conditions.includes('node')) { + inlineConditions.push('browser') + } + } else if (!conditions.includes('browser')) { + inlineConditions.push('node') } - if (options.overrideConditions) { - conditions.push( - ...options.overrideConditions.filter((condition) => - conditionalConditions.has(condition) - ) - ) - } else if (options.conditions.length > 0) { - conditions.push(...options.conditions) + + // The "module" condition is no longer recommended, but some older + // packages may still use it. + if (!options.isRequire && !conditions.includes('require')) { + inlineConditions.push('module') } - return _resolveExports(pkg, key, { - browser: targetWeb && !conditions.includes('node'), - require: options.isRequire && !conditions.includes('import'), - conditions + // The "overrideConditions" array can add arbitrary conditions. + options.overrideConditions?.forEach((condition) => { + if (!inlineConditions.includes(condition)) { + inlineConditions.push(condition) + } }) + + return inlineConditions } function resolveDeepImport( @@ -1105,47 +1086,58 @@ function resolveDeepImport( return cache } - let relativeId: string | undefined | void = id const { exports: exportsField, browser: browserField } = data + const { file, postfix } = splitFileAndPostfix(id) - // map relative based on exports data + let possibleFiles: string[] | undefined if (exportsField) { - if (isObject(exportsField) && !Array.isArray(exportsField)) { - // resolve without postfix (see #7098) - const { file, postfix } = splitFileAndPostfix(relativeId) - const exportsId = resolveExports(data, file, options, targetWeb) - if (exportsId !== undefined) { - relativeId = exportsId + postfix + // map relative based on exports data + possibleFiles = resolveExports( + data, + file, + options, + getInlineConditions(options, targetWeb), + options.overrideConditions + ) + if (postfix) { + if (possibleFiles.length) { + possibleFiles = possibleFiles.map((f) => f + postfix) } else { - relativeId = undefined + possibleFiles = resolveExports( + data, + file + postfix, + options, + getInlineConditions(options, targetWeb), + options.overrideConditions + ) } - } else { - // not exposed - relativeId = undefined } - if (!relativeId) { + if (!possibleFiles.length) { throw new Error( - `Package subpath '${relativeId}' is not defined by "exports" in ` + + `Package subpath '${file}' is not defined by "exports" in ` + `${path.join(dir, 'package.json')}.` ) } } else if (targetWeb && options.browserField && isObject(browserField)) { - // resolve without postfix (see #7098) - const { file, postfix } = splitFileAndPostfix(relativeId) const mapped = mapWithBrowserField(file, browserField) if (mapped) { - relativeId = mapped + postfix + possibleFiles = [mapped + postfix] } else if (mapped === false) { return (webResolvedImports[id] = browserExternalId) } } - if (relativeId) { - const resolved = tryFsResolve( - path.join(dir, relativeId), - options, - !exportsField, // try index only if no exports field - targetWeb + possibleFiles ||= [id] + if (possibleFiles[0]) { + let resolved: string | undefined + possibleFiles.some( + (file) => + (resolved = tryFsResolve( + path.join(dir, file), + options, + !exportsField, // try index only if no exports field + targetWeb + )) ) if (resolved) { isDebug && diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7f8770e4973ee..3f1369b739fd2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,7 +260,7 @@ importers: postcss-load-config: ^4.0.1 postcss-modules: ^5.0.0 resolve: ^1.22.1 - resolve.exports: ^1.1.0 + resolve.exports: npm:@alloc/resolve.exports@^1.1.0 rollup: ~3.3.0 sirv: ^2.0.2 source-map-js: ^1.0.2 @@ -323,7 +323,7 @@ importers: postcss-import: 15.0.0_postcss@8.4.19 postcss-load-config: 4.0.1_postcss@8.4.19 postcss-modules: 5.0.0_postcss@8.4.19 - resolve.exports: 1.1.0 + resolve.exports: /@alloc/resolve.exports/1.1.0 sirv: 2.0.2 source-map-js: 1.0.2 source-map-support: 0.5.21 @@ -1420,6 +1420,11 @@ packages: '@algolia/requester-common': 4.13.1 dev: true + /@alloc/resolve.exports/1.1.0: + resolution: {integrity: sha512-daZJ4gBXxPUgjWjtxRp+5mU9tV6k7cSG2iKFyiPZOTxRzMRFPEe8dcTuqP+zIVSTfFpN1/SCIOMMYeYA7GwQvQ==} + engines: {node: '>=10'} + dev: true + /@ampproject/remapping/2.2.0: resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} engines: {node: '>=6.0.0'} @@ -7772,11 +7777,6 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - /resolve.exports/1.1.0: - resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==} - engines: {node: '>=10'} - dev: true - /resolve/1.17.0: resolution: {integrity: sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==} dependencies: