diff --git a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts index 07d8cab4566b40..8a0015790f95cf 100644 --- a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts +++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts @@ -1,7 +1,7 @@ import path from 'node:path' import MagicString from 'magic-string' import type { ImportKind, Plugin, RolldownPlugin } from 'rolldown' -import { prefixRegex } from 'rolldown/filter' +import { exactRegex, prefixRegex } from 'rolldown/filter' import { stripLiteral } from 'strip-literal' import { isWindows } from '../../shared/utils' import { JS_TYPES_RE, KNOWN_ASSET_TYPES } from '../constants' @@ -121,6 +121,11 @@ export function rolldownDepPlugin( } const resolveResult = (id: string, resolved: string, kind: ImportKind) => { + // An exact browser-external id is an explicit browser:false mapping. + // Suffixed ids are unsupported Node builtins and still need the warning. + if (resolved === browserExternalId) { + return { id: browserExternalId } + } if (resolved.startsWith(browserExternalId)) { return { id: browserExternalNamespace + id, @@ -255,11 +260,15 @@ export function rolldownDepPlugin( load: { filter: { id: [ + exactRegex(browserExternalId), prefixRegex(browserExternalNamespace), prefixRegex(optionalPeerDepNamespace), ], }, handler(id) { + if (id === browserExternalId) { + return { code: 'module.exports = {}' } + } if (id.startsWith(browserExternalNamespace)) { const path = id.slice(browserExternalNamespace.length) if (isProduction) { diff --git a/playground/optimize-deps/__tests__/optimize-deps.spec.ts b/playground/optimize-deps/__tests__/optimize-deps.spec.ts index d6febaafad2c17..7754b0e1887e27 100644 --- a/playground/optimize-deps/__tests__/optimize-deps.spec.ts +++ b/playground/optimize-deps/__tests__/optimize-deps.spec.ts @@ -255,6 +255,20 @@ test('non optimized module is not duplicated', async () => { .toBe('from-absolute-path, from-relative-path') }) +test.runIf(isServe)( + 'optimized browser:false is empty without warning', + async () => { + expect(await page.textContent('.browser-false-optimized')).toBe('[success]') + expect(browserLogs).not.toEqual( + expect.arrayContaining([ + expect.stringContaining( + 'Module "browser-false-only" has been externalized for browser compatibility', + ), + ]), + ) + }, +) + test.runIf(isServe)('error on builtin modules usage', () => { expect(browserLogs).toEqual( expect.arrayContaining([ diff --git a/playground/optimize-deps/dep-browser-false/index.js b/playground/optimize-deps/dep-browser-false/index.js new file mode 100644 index 00000000000000..2c7cc4dea71645 --- /dev/null +++ b/playground/optimize-deps/dep-browser-false/index.js @@ -0,0 +1,4 @@ +// An explicit browser:false mapping is an empty module, not a Node builtin warning. +// eslint-disable-next-line n/no-missing-require -- mapped to false by package browser field +const fs = require('browser-false-only') +module.exports = fs.readFileSync === undefined ? '[success]' : '[fail]' diff --git a/playground/optimize-deps/dep-browser-false/package.json b/playground/optimize-deps/dep-browser-false/package.json new file mode 100644 index 00000000000000..57732ee0c5f41c --- /dev/null +++ b/playground/optimize-deps/dep-browser-false/package.json @@ -0,0 +1,9 @@ +{ + "name": "@vitejs/test-dep-browser-false", + "private": true, + "version": "0.0.0", + "main": "index.js", + "browser": { + "browser-false-only": false + } +} diff --git a/playground/optimize-deps/index.html b/playground/optimize-deps/index.html index 2e3b6607dabff7..6f351818c61a1b 100644 --- a/playground/optimize-deps/index.html +++ b/playground/optimize-deps/index.html @@ -336,6 +336,12 @@

Import the CommonJS external package that omits the js suffix

} +

[pending]

+ +