diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index f1878e89d17433..13ac18ae384371 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -3,6 +3,7 @@ import path from 'node:path' import colors from 'picocolors' import type { PartialResolvedId } from 'rollup' import { resolve as _resolveExports } from 'resolve.exports' +import { hasESMSyntax } from 'mlly' import type { Plugin } from '../plugin' import { DEFAULT_EXTENSIONS, @@ -850,9 +851,9 @@ export function resolvePackageEntry( ) { // if both are present, we may have a problem: some package points both // to ESM, with "module" targeting Node.js, while some packages points - // "module" to browser ESM and "browser" to UMD. + // "module" to browser ESM and "browser" to UMD/IIFE. // the heuristics here is to actually read the browser entry when - // possible and check for hints of UMD. If it is UMD, prefer "module" + // possible and check for hints of ESM. If it is not ESM, prefer "module" // instead; Otherwise, assume it's ESM and use it. const resolvedBrowserEntry = tryFsResolve( path.join(dir, browserEntry), @@ -860,15 +861,12 @@ export function resolvePackageEntry( ) if (resolvedBrowserEntry) { const content = fs.readFileSync(resolvedBrowserEntry, 'utf-8') - if ( - (/typeof exports\s*==/.test(content) && - /typeof module\s*==/.test(content)) || - /module\.exports\s*=/.test(content) - ) { - // likely UMD or CJS(!!! e.g. firebase 7.x), prefer module - entryPoint = data.module - } else { + if (hasESMSyntax(content)) { + // likely ESM, prefer browser entryPoint = browserEntry + } else { + // non-ESM, UMD or IIFE or CJS(!!! e.g. firebase 7.x), prefer module + entryPoint = data.module } } } else { diff --git a/playground/resolve/__tests__/resolve.spec.ts b/playground/resolve/__tests__/resolve.spec.ts index 8d788949b67e32..784e0a4de32cea 100644 --- a/playground/resolve/__tests__/resolve.spec.ts +++ b/playground/resolve/__tests__/resolve.spec.ts @@ -96,6 +96,10 @@ test('Resolve module field if browser field is likely UMD or CJS', async () => { expect(await page.textContent('.browser-module2')).toMatch('[success]') }) +test('Resolve module field if browser field is likely IIFE', async () => { + expect(await page.textContent('.browser-module3')).toMatch('[success]') +}) + test('css entry', async () => { expect(await page.textContent('.css')).toMatch('[success]') }) diff --git a/playground/resolve/browser-module-field3/index.js b/playground/resolve/browser-module-field3/index.js new file mode 100644 index 00000000000000..99af62f8e3700e --- /dev/null +++ b/playground/resolve/browser-module-field3/index.js @@ -0,0 +1 @@ +export default '[success] this should run in browser' diff --git a/playground/resolve/browser-module-field3/index.web.js b/playground/resolve/browser-module-field3/index.web.js new file mode 100644 index 00000000000000..843b376e2c4daa --- /dev/null +++ b/playground/resolve/browser-module-field3/index.web.js @@ -0,0 +1,7 @@ +var browserModuleField3 = (function () { + 'use strict' + + var main = '[fail] this should not run in the browser' + + return main +})() diff --git a/playground/resolve/browser-module-field3/package.json b/playground/resolve/browser-module-field3/package.json new file mode 100644 index 00000000000000..931c6a0ebd9eb4 --- /dev/null +++ b/playground/resolve/browser-module-field3/package.json @@ -0,0 +1,7 @@ +{ + "name": "resolve-browser-module-field3", + "private": true, + "version": "1.0.0", + "module": "index.js", + "browser": "index.web.js" +} diff --git a/playground/resolve/index.html b/playground/resolve/index.html index 7502c422e0eb82..c744d4b9317463 100644 --- a/playground/resolve/index.html +++ b/playground/resolve/index.html @@ -82,6 +82,9 @@

Resolve browser field even if module field exists

Resolve module field if browser field is likely UMD or CJS

fail

+

Resolve module field if browser field is likely IIFE

+

fail

+

Don't resolve to the `module` field if the importer is a `require` call

fail

@@ -225,6 +228,9 @@

resolve package that contains # in path

import browserModule2 from 'resolve-browser-module-field2' text('.browser-module2', browserModule2) + import browserModule3 from 'resolve-browser-module-field3' + text('.browser-module3', browserModule3) + import { msg as requireButWithModuleFieldMsg } from 'require-pkg-with-module-field' text('.require-pkg-with-module-field', requireButWithModuleFieldMsg) diff --git a/playground/resolve/package.json b/playground/resolve/package.json index 5763ad88b79ead..1b8e76e353a7ff 100644 --- a/playground/resolve/package.json +++ b/playground/resolve/package.json @@ -16,6 +16,7 @@ "resolve-browser-field": "link:./browser-field", "resolve-browser-module-field1": "link:./browser-module-field1", "resolve-browser-module-field2": "link:./browser-module-field2", + "resolve-browser-module-field3": "link:./browser-module-field3", "resolve-custom-condition": "link:./custom-condition", "resolve-custom-main-field": "link:./custom-main-field", "resolve-exports-env": "link:./exports-env", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ae863cf61692e..48c2030951703a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -847,6 +847,7 @@ importers: resolve-browser-field: link:./browser-field resolve-browser-module-field1: link:./browser-module-field1 resolve-browser-module-field2: link:./browser-module-field2 + resolve-browser-module-field3: link:./browser-module-field3 resolve-custom-condition: link:./custom-condition resolve-custom-main-field: link:./custom-main-field resolve-exports-env: link:./exports-env @@ -860,6 +861,7 @@ importers: resolve-browser-field: link:browser-field resolve-browser-module-field1: link:browser-module-field1 resolve-browser-module-field2: link:browser-module-field2 + resolve-browser-module-field3: link:browser-module-field3 resolve-custom-condition: link:custom-condition resolve-custom-main-field: link:custom-main-field resolve-exports-env: link:exports-env @@ -881,6 +883,9 @@ importers: playground/resolve/browser-module-field2: specifiers: {} + playground/resolve/browser-module-field3: + specifiers: {} + playground/resolve/custom-condition: specifiers: {}