Skip to content

Commit 4a111aa

Browse files
authoredNov 29, 2023
fix(resolve): respect order of browser in mainFields when resolving (#15137)
1 parent 793cebd commit 4a111aa

File tree

11 files changed

+98
-44
lines changed

11 files changed

+98
-44
lines changed
 

‎packages/vite/src/node/plugins/resolve.ts

+55-44
Original file line numberDiff line numberDiff line change
@@ -982,53 +982,17 @@ export function resolvePackageEntry(
982982
)
983983
}
984984

985-
// handle edge case with browser and module field semantics
986-
if (!entryPoint && targetWeb && options.mainFields.includes('browser')) {
987-
// check browser field
988-
// https://github.com/defunctzombie/package-browser-field-spec
989-
const browserEntry =
990-
typeof data.browser === 'string'
991-
? data.browser
992-
: isObject(data.browser) && data.browser['.']
993-
if (browserEntry) {
994-
// check if the package also has a "module" field.
995-
if (
996-
!options.isRequire &&
997-
options.mainFields.includes('module') &&
998-
typeof data.module === 'string' &&
999-
data.module !== browserEntry
1000-
) {
1001-
// if both are present, we may have a problem: some package points both
1002-
// to ESM, with "module" targeting Node.js, while some packages points
1003-
// "module" to browser ESM and "browser" to UMD/IIFE.
1004-
// the heuristics here is to actually read the browser entry when
1005-
// possible and check for hints of ESM. If it is not ESM, prefer "module"
1006-
// instead; Otherwise, assume it's ESM and use it.
1007-
const resolvedBrowserEntry = tryFsResolve(
1008-
path.join(dir, browserEntry),
1009-
options,
1010-
)
1011-
if (resolvedBrowserEntry) {
1012-
const content = fs.readFileSync(resolvedBrowserEntry, 'utf-8')
1013-
if (hasESMSyntax(content)) {
1014-
// likely ESM, prefer browser
1015-
entryPoint = browserEntry
1016-
} else {
1017-
// non-ESM, UMD or IIFE or CJS(!!! e.g. firebase 7.x), prefer module
1018-
entryPoint = data.module
1019-
}
1020-
}
1021-
} else {
1022-
entryPoint = browserEntry
1023-
}
1024-
}
1025-
}
1026-
1027985
// fallback to mainFields if still not resolved
1028986
if (!entryPoint) {
1029987
for (const field of options.mainFields) {
1030-
if (field === 'browser') continue // already checked above
1031-
if (typeof data[field] === 'string') {
988+
if (field === 'browser') {
989+
if (targetWeb) {
990+
entryPoint = tryResolveBrowserEntry(dir, data, options)
991+
if (entryPoint) {
992+
break
993+
}
994+
}
995+
} else if (typeof data[field] === 'string') {
1032996
entryPoint = data[field]
1033997
break
1034998
}
@@ -1257,6 +1221,53 @@ function tryResolveBrowserMapping(
12571221
}
12581222
}
12591223

1224+
function tryResolveBrowserEntry(
1225+
dir: string,
1226+
data: PackageData['data'],
1227+
options: InternalResolveOptions,
1228+
) {
1229+
// handle edge case with browser and module field semantics
1230+
1231+
// check browser field
1232+
// https://github.com/defunctzombie/package-browser-field-spec
1233+
const browserEntry =
1234+
typeof data.browser === 'string'
1235+
? data.browser
1236+
: isObject(data.browser) && data.browser['.']
1237+
if (browserEntry) {
1238+
// check if the package also has a "module" field.
1239+
if (
1240+
!options.isRequire &&
1241+
options.mainFields.includes('module') &&
1242+
typeof data.module === 'string' &&
1243+
data.module !== browserEntry
1244+
) {
1245+
// if both are present, we may have a problem: some package points both
1246+
// to ESM, with "module" targeting Node.js, while some packages points
1247+
// "module" to browser ESM and "browser" to UMD/IIFE.
1248+
// the heuristics here is to actually read the browser entry when
1249+
// possible and check for hints of ESM. If it is not ESM, prefer "module"
1250+
// instead; Otherwise, assume it's ESM and use it.
1251+
const resolvedBrowserEntry = tryFsResolve(
1252+
path.join(dir, browserEntry),
1253+
options,
1254+
)
1255+
if (resolvedBrowserEntry) {
1256+
const content = fs.readFileSync(resolvedBrowserEntry, 'utf-8')
1257+
if (hasESMSyntax(content)) {
1258+
// likely ESM, prefer browser
1259+
return browserEntry
1260+
} else {
1261+
// non-ESM, UMD or IIFE or CJS(!!! e.g. firebase 7.x), prefer module
1262+
return data.module
1263+
}
1264+
}
1265+
} else {
1266+
return browserEntry
1267+
}
1268+
}
1269+
}
1270+
12601271
/**
12611272
* given a relative path in pkg dir,
12621273
* return a relative path in pkg dir,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { expect, test } from 'vitest'
2+
import { page } from '~utils'
3+
4+
test('resolve.mainFields.custom-first', async () => {
5+
expect(await page.textContent('.custom-browser-main-field')).toBe(
6+
'resolved custom field',
7+
)
8+
})

‎playground/resolve/__tests__/resolve.spec.ts

+6
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ test('resolve.mainFields', async () => {
167167
expect(await page.textContent('.custom-main-fields')).toMatch('[success]')
168168
})
169169

170+
test('resolve.mainFields.browser-first', async () => {
171+
expect(await page.textContent('.custom-browser-main-field')).toBe(
172+
'resolved browser field',
173+
)
174+
})
175+
170176
test('resolve.conditions', async () => {
171177
expect(await page.textContent('.custom-condition')).toMatch('[success]')
172178
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const msg = 'resolved browser field'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const msg = 'resolved custom field'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const msg = '[fail] resolved main field'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "@vitejs/test-resolve-custom-browser-main-field",
3+
"private": true,
4+
"version": "1.0.0",
5+
"main": "index.js",
6+
"browser": "index.browser.js",
7+
"custom": "index.custom.js"
8+
}

‎playground/resolve/index.html

+6
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ <h2>resolve.extensions</h2>
158158
<h2>resolve.mainFields</h2>
159159
<p class="custom-main-fields"></p>
160160

161+
<h2>resolve.mainFields.custom-browser-main</h2>
162+
<p class="custom-browser-main-field"></p>
163+
161164
<h2>resolve.conditions</h2>
162165
<p class="custom-condition"></p>
163166

@@ -341,6 +344,9 @@ <h2>resolve package that contains # in path</h2>
341344
import { msg as customMainMsg } from '@vitejs/test-resolve-custom-main-field'
342345
text('.custom-main-fields', customMainMsg)
343346

347+
import { msg as customBrowserMsg } from '@vitejs/test-resolve-custom-browser-main-field'
348+
text('.custom-browser-main-field', customBrowserMsg)
349+
344350
import { msg as customConditionMsg } from '@vitejs/test-resolve-custom-condition'
345351
text('.custom-condition', customConditionMsg)
346352

‎playground/resolve/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@vitejs/test-resolve-browser-module-field3": "link:./browser-module-field3",
2929
"@vitejs/test-resolve-custom-condition": "link:./custom-condition",
3030
"@vitejs/test-resolve-custom-main-field": "link:./custom-main-field",
31+
"@vitejs/test-resolve-custom-browser-main-field": "link:./custom-browser-main-field",
3132
"@vitejs/test-resolve-exports-and-nested-scope": "link:./exports-and-nested-scope",
3233
"@vitejs/test-resolve-exports-env": "link:./exports-env",
3334
"@vitejs/test-resolve-exports-from-root": "link:./exports-from-root",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import config from './vite.config.js'
2+
config.resolve.mainFields = [
3+
'custom',
4+
...config.resolve.mainFields.filter((f) => f !== 'custom'),
5+
]
6+
export default config

‎pnpm-lock.yaml

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.