Repository navigation
fix(build): handle rolldown external requires in nodeless builds #4136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
23e14ce
04a0536
714eac9
85c8139
d72a7ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| import type { Nitro } from "nitro/types"; | ||
| import type { OutputOptions, RolldownOptions, RolldownPlugin } from "rolldown"; | ||
| import { viteAliasPlugin } from "rolldown/experimental"; | ||
| import { esmExternalRequirePlugin } from "rolldown/plugins"; | ||
| import { baseBuildConfig } from "../config.ts"; | ||
| import { baseBuildPlugins } from "../plugins.ts"; | ||
| import { builtinModules } from "node:module"; | ||
|
|
@@ -8,17 +10,36 @@ import { getChunkName, libChunkName, NODE_MODULES_RE } from "../chunks.ts"; | |
|
|
||
| export const getRolldownConfig = async (nitro: Nitro): Promise<RolldownOptions> => { | ||
| const base = baseBuildConfig(nitro); | ||
| const isNodeless = nitro.options.node === false; | ||
| const builtinExternal = isNodeless | ||
| ? [] | ||
| : [...builtinModules, ...builtinModules.map((m) => `node:${m}`)]; | ||
| const nodeBuiltinAliases = getNodeBuiltinAliases(base.aliases); | ||
| const external = isNodeless | ||
| ? base.env.external.filter((id) => !id.startsWith("node:")) | ||
| : [...base.env.external, ...builtinExternal]; | ||
|
|
||
| const tsc = nitro.options.typescript.tsConfig?.compilerOptions; | ||
|
|
||
| let config: RolldownOptions = { | ||
| platform: nitro.options.node ? "node" : "neutral", | ||
| cwd: nitro.options.rootDir, | ||
| input: nitro.options.entry, | ||
| external: [...base.env.external, ...builtinModules, ...builtinModules.map((m) => `node:${m}`)], | ||
| plugins: [...((await baseBuildPlugins(nitro, base)) as RolldownPlugin[])], | ||
| external, | ||
| plugins: [ | ||
| ...(isNodeless | ||
| ? [ | ||
| ...(nodeBuiltinAliases.length > 0 | ||
| ? [viteAliasPlugin({ entries: nodeBuiltinAliases })] | ||
| : []), | ||
| nodeBuiltinImportExternalPlugin(), | ||
| esmExternalRequirePlugin({ external: [/^node:/] }), | ||
| ] | ||
| : []), | ||
| ...((await baseBuildPlugins(nitro, base)) as RolldownPlugin[]), | ||
| ], | ||
| resolve: { | ||
| alias: base.aliases, | ||
| alias: isNodeless ? omitAliases(base.aliases, nodeBuiltinAliases) : base.aliases, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: # First, let's understand the file structure and read the relevant sections
head -150 src/build/rolldown/config.ts | tail -n +1 | cat -nRepository: nitrojs/nitro Length of output: 5621 π Script executed: # Get the file size first
wc -l src/build/rolldown/config.tsRepository: nitrojs/nitro Length of output: 89 π Script executed: # Read the key sections: around line 42 and 88-92
sed -n '1,110p' src/build/rolldown/config.ts | cat -nRepository: nitrojs/nitro Length of output: 4744 π Script executed: # Search for helper functions like omitAliases and defu imports
rg -n "omitAliases|defu|import.*defu" src/build/rolldown/config.ts -A 2 -B 2Repository: nitrojs/nitro Length of output: 792 π Script executed: # Check where defu is imported from
rg -n "import.*defu" src/build/ -A 1 -B 1Repository: nitrojs/nitro Length of output: 1304 Add post-merge sanitization for Line 42 sanitizes π€ Prompt for AI Agents |
||
| extensions: base.extensions, | ||
| conditionNames: nitro.options.exportConditions, | ||
| }, | ||
|
|
@@ -70,6 +91,10 @@ export const getRolldownConfig = async (nitro: Nitro): Promise<RolldownOptions> | |
| config | ||
| ); | ||
|
|
||
| if (isNodeless && Array.isArray(config.external)) { | ||
| config.external = config.external.filter((id) => !String(id).startsWith("node:")); | ||
|
Comment on lines
+94
to
+95
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: cat -n src/build/rolldown/config.ts | head -150Repository: nitrojs/nitro Length of output: 5621 π Script executed: # Search for references to config.external and rolldownConfig/rollupConfig to understand override patterns
rg "rolldownConfig|rollupConfig" src/ -A 3 -B 3 --type tsRepository: nitrojs/nitro Length of output: 13573 π Script executed: # Check Rolldown/Rollup types for external property to see what types it accepts
fd "node_modules" -type d -prune -o -type f \( -name "*.d.ts" -o -name "package.json" \) | head -20
# Let's check what types can be assigned to external in rolldown config
rg "external\s*:" src/build/rolldown/config.ts -B 5 -A 5Repository: nitrojs/nitro Length of output: 898 π Script executed: # Search for where external is actually used in the rolldown plugins or build process
rg "config\.external|external\s*[=:]" src/build/ --type ts -B 2 -A 2Repository: nitrojs/nitro Length of output: 2931 π Script executed: cat -n src/presets/deno/preset.ts | grep -A 5 -B 5 "external:"Repository: nitrojs/nitro Length of output: 888 π Script executed: # Check the esmExternalRequirePlugin behavior - does it handle function-based external?
rg "esmExternalRequirePlugin" src/ -B 5 -A 10Repository: nitrojs/nitro Length of output: 2208 π Script executed: # Look for any test files or documentation about external handling in nodeless mode
fd "test" -type f | xargs rg "external|nodeless" 2>/dev/null | head -30Repository: nitrojs/nitro Length of output: 3125 π Script executed: # Check if Deno preset sets node: false (nodeless)
rg "node.*false" src/presets/deno/preset.tsRepository: nitrojs/nitro Length of output: 73 π Script executed: # Check the base-worker preset since it explicitly sets node: false
cat -n src/presets/_nitro/base-worker.ts | head -40Repository: nitrojs/nitro Length of output: 716 π Script executed: # Search for any preset that combines node: false with a function-based external
rg -B 10 "external\s*:\s*\(" src/presets/ | grep -A 10 "node.*false"Repository: nitrojs/nitro Length of output: 651 π Script executed: # Check how defu merges - does it override or merge external configs?
rg "defu" src/build/rolldown/config.ts -B 2 -A 5Repository: nitrojs/nitro Length of output: 587 π Script executed: # Let's check if the nodeBuiltinImportExternalPlugin provides a safeguard
cat -n src/build/rolldown/config.ts | sed -n '125,135p'Repository: nitrojs/nitro Length of output: 480 π Script executed: # Check if there are any other safeguards in the rolldown/rollup plugins
rg "node:" src/build/plugins/ -B 2 -A 2Repository: nitrojs/nitro Length of output: 1474 π Script executed: # Verify the Deno preset setup one more time - checking both node: false and external function
cat -n src/presets/deno/preset.ts | head -50Repository: nitrojs/nitro Length of output: 1928 The post-merge When a preset provides π€ Prompt for AI Agents |
||
| } | ||
|
|
||
| const outputConfig = config.output as OutputOptions; | ||
| if (outputConfig.inlineDynamicImports || outputConfig.format === "iife") { | ||
| delete outputConfig.inlineDynamicImports; | ||
|
|
@@ -78,3 +103,33 @@ export const getRolldownConfig = async (nitro: Nitro): Promise<RolldownOptions> | |
|
|
||
| return config as RolldownOptions; | ||
| }; | ||
|
|
||
| function getNodeBuiltinAliases(aliases: Record<string, string>) { | ||
| return Object.entries(aliases) | ||
| .filter(([find, replacement]) => !find.startsWith("node:") && replacement.startsWith("node:")) | ||
| .map(([find, replacement]) => ({ | ||
| key: find, | ||
| find, | ||
| replacement, | ||
| })); | ||
| } | ||
|
|
||
| function omitAliases( | ||
| aliases: Record<string, string>, | ||
| omittedAliases: { key: string; find: string; replacement: string }[] | ||
| ) { | ||
| const omitted = new Set(omittedAliases.map((entry) => entry.key)); | ||
| return Object.fromEntries(Object.entries(aliases).filter(([key]) => !omitted.has(key))); | ||
| } | ||
|
|
||
| function nodeBuiltinImportExternalPlugin(): RolldownPlugin { | ||
| return { | ||
| name: "nitro:rolldown-node-builtin-import-external", | ||
| resolveId(source, _importer, extraOptions) { | ||
| if (!source.startsWith("node:") || extraOptions.kind === "require-call") { | ||
| return null; | ||
| } | ||
| return { id: source, external: true }; | ||
| }, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| const { EventEmitter } = require("events"); | ||
|
|
||
| class Client extends EventEmitter {} | ||
|
|
||
| module.exports = { | ||
| Client, | ||
| EventEmitter, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import client from "./cjs-builtin-require.cjs"; | ||
|
|
||
| export const Client = client.Client; | ||
| export const EventEmitter = client.EventEmitter; | ||
| export default client; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { defineEventHandler } from "h3"; | ||
| import builtin from "../../lib/cjs-builtin-require.mjs"; | ||
|
|
||
| export default defineEventHandler(() => { | ||
| return { | ||
| ok: true, | ||
| clientName: builtin.Client.name, | ||
| eventEmitterType: typeof builtin.EventEmitter, | ||
| }; | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.