Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ async function prepareRolldownOptimizerRun(

const plugins = await asyncFlatten(arraify(pluginsFromConfig))
if (external.length) {
plugins.push(rolldownCjsExternalPlugin(external, platform))
plugins.push(rolldownCjsExternalPlugin(external, platform, environment))
}
plugins.push(...rolldownDepPlugin(environment, flatIdDeps, external))

Expand Down
21 changes: 20 additions & 1 deletion packages/vite/src/node/optimizer/rolldownDepPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$`
export function rolldownCjsExternalPlugin(
externals: string[],
platform: 'node' | 'browser' | 'neutral',
environment: Environment,
): Plugin | undefined {
// Skip this plugin for `platform: 'node'` as `require` is available in Node
// and that is more accurate than converting to `import`
Expand All @@ -393,18 +394,36 @@ export function rolldownCjsExternalPlugin(

const filter = new RegExp(externals.map(matchesEntireLine).join('|'))

const packageCache: PackageCache = new Map()
const resolveRequire = createBackCompatIdResolver(
environment.getTopLevelConfig(),
{ asSrc: false, isRequire: true, scan: true, packageCache },
)

return {
name: 'cjs-external',
resolveId: {
filter: { id: [prefixRegex(nonFacadePrefix), filter] },
handler(id, _importer, options) {
async handler(id, importer, options) {
if (id.startsWith(nonFacadePrefix)) {
return {
id: id.slice(nonFacadePrefix.length),
external: 'absolute',
}
}
if (options.kind === 'require-call') {
// A missing optional peer must throw when require() runs, not when
// an eager ESM facade is evaluated. Reuse the pre-bundler's CJS stub
// so the dependency's try/catch can still select its fallback.
// Other excluded deps may intentionally be unresolvable until served.
const resolved = await resolveRequire(
environment,
id,
importer,
).catch(() => undefined)
if (resolved?.startsWith(optionalPeerDepId)) {
return { id: optionalPeerDepNamespace + resolved }
}
return {
id: cjsExternalFacadeNamespace + id,
}
Expand Down
14 changes: 14 additions & 0 deletions playground/optimize-deps/__tests__/optimize-deps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,20 @@ test('dep with optional peer dep (cjs)', async () => {
.toMatch(`[success]`)
})

test.runIf(isServe)(
'optimized CJS dep preserves fallback for excluded optional peer',
async () => {
const metadata = readDepOptimizationMetadata()
expect(Object.keys(metadata.optimized)).toContain(
'@vitejs/test-dep-with-excluded-optional-peer-dep-cjs',
)

await expect
.poll(() => page.textContent('.dep-with-excluded-optional-peer-dep-cjs'))
.toMatch(`[success]`)
},
)

test('dep with css import', async () => {
await expect.poll(() => getColor('.dep-linked-include')).toBe('red')
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
exports.callPeerDep = function () {
try {
// This should not be hoisted as static import
// eslint-disable-next-line n/no-missing-require
return require('@vitejs/test-excluded-optional-peer')
} catch {
return 'fallback'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@vitejs/test-dep-with-excluded-optional-peer-dep-cjs",
"private": true,
"version": "0.0.0",
"main": "index.js",
"peerDependencies": {
"@vitejs/test-excluded-optional-peer": "0.0.0"
},
"peerDependenciesMeta": {
"@vitejs/test-excluded-optional-peer": {
"optional": true
}
}
}
12 changes: 12 additions & 0 deletions playground/optimize-deps/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ <h2>Import from dependency with optional peer dep (cjs)</h2>
<div class="dep-with-optional-peer-dep-cjs"></div>
<div class="dep-with-optional-peer-dep-cjs-error"></div>

<h2>Import from optimized dependency with excluded optional peer dep (cjs)</h2>
<div class="dep-with-excluded-optional-peer-dep-cjs"></div>

<h2>Externalize known non-js files in optimize included dep</h2>
<div class="externalize-known-non-js"></div>

Expand Down Expand Up @@ -311,6 +314,15 @@ <h2>Import the CommonJS external package that omits the js suffix</h2>
)
</script>

<script type="module">
import { callPeerDep } from '@vitejs/test-dep-with-excluded-optional-peer-dep-cjs'

text(
'.dep-with-excluded-optional-peer-dep-cjs',
callPeerDep() === 'fallback' ? '[success]' : `[failed]: did not fallback`,
)
</script>

<script type="module">
// should error on builtin modules (named import)
// no node: protocol intentionally
Expand Down
1 change: 1 addition & 0 deletions playground/optimize-deps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@vitejs/test-dep-with-builtin-module-cjs": "file:./dep-with-builtin-module-cjs",
"@vitejs/test-dep-with-builtin-module-esm": "file:./dep-with-builtin-module-esm",
"@vitejs/test-dep-with-dynamic-import": "file:./dep-with-dynamic-import",
"@vitejs/test-dep-with-excluded-optional-peer-dep-cjs": "file:./dep-with-excluded-optional-peer-dep-cjs",
"@vitejs/test-dep-with-optional-peer-dep": "file:./dep-with-optional-peer-dep",
"@vitejs/test-dep-with-optional-peer-dep-cjs": "file:./dep-with-optional-peer-dep-cjs",
"@vitejs/test-dep-with-optional-peer-dep-submodule": "file:./dep-with-optional-peer-dep-submodule",
Expand Down
1 change: 1 addition & 0 deletions playground/optimize-deps/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default defineConfig({
'@vitejs/test-dep-cjs-for-injected-import',
],
exclude: [
'@vitejs/test-excluded-optional-peer',
'@vitejs/test-nested-exclude',
'@vitejs/test-dep-non-optimized',
'@vitejs/test-dep-esm-external',
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading