Skip to content

Commit ff04bbb

Browse files
committed
fix(rolldown): resolve cjs require regressions for worker targets
1 parent e102ff6 commit ff04bbb

3 files changed

Lines changed: 143 additions & 2 deletions

File tree

src/build/rolldown/config.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
import type { Nitro } from "nitro/types";
22
import type { OutputOptions, RolldownOptions, RolldownPlugin } from "rolldown";
3+
import { esmExternalRequirePlugin } from "rolldown/plugins";
34
import { baseBuildConfig } from "../config.ts";
45
import { baseBuildPlugins } from "../plugins.ts";
56
import { builtinModules } from "node:module";
67
import { defu } from "defu";
78
import { getChunkName, libChunkName, NODE_MODULES_RE } from "../chunks.ts";
9+
import { requireConditionResolver } from "./require-condition.ts";
810

911
export const getRolldownConfig = async (nitro: Nitro): Promise<RolldownOptions> => {
1012
const base = baseBuildConfig(nitro);
13+
const builtinExternals = [...builtinModules, ...builtinModules.map((m) => `node:${m}`)];
14+
const plugins: RolldownPlugin[] = [
15+
requireConditionResolver(nitro),
16+
...(!nitro.options.node
17+
? [
18+
esmExternalRequirePlugin({
19+
external: builtinExternals,
20+
skipDuplicateCheck: true,
21+
}) as RolldownPlugin,
22+
]
23+
: []),
24+
...((await baseBuildPlugins(nitro, base)) as RolldownPlugin[]),
25+
];
1126

1227
const tsc = nitro.options.typescript.tsConfig?.compilerOptions;
1328

1429
let config: RolldownOptions = {
1530
platform: nitro.options.node ? "node" : "neutral",
1631
cwd: nitro.options.rootDir,
1732
input: nitro.options.entry,
18-
external: [...base.env.external, ...builtinModules, ...builtinModules.map((m) => `node:${m}`)],
19-
plugins: [...((await baseBuildPlugins(nitro, base)) as RolldownPlugin[])],
33+
external: nitro.options.node
34+
? [...base.env.external, ...builtinExternals]
35+
: [...base.env.external],
36+
plugins,
2037
resolve: {
2138
alias: base.aliases,
2239
extensions: base.extensions,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { Nitro } from "nitro/types";
2+
import type { RolldownPlugin } from "rolldown";
3+
4+
import { builtinModules } from "node:module";
5+
import { resolveModulePath } from "exsolve";
6+
import { isAbsolute } from "pathe";
7+
8+
const RESOLVE_SKIP_RE = /^(?:[\0#~.]|[a-z0-9]{2,}:)|\?/;
9+
const BUILTIN_MODULE_SET = new Set([
10+
...builtinModules,
11+
...builtinModules.map((id) => `node:${id}`),
12+
]);
13+
14+
export function getRequireConditionNames(exportConditions: string[]) {
15+
return [
16+
...new Set([
17+
"require",
18+
...exportConditions.filter((condition) => condition !== "import"),
19+
"default",
20+
]),
21+
];
22+
}
23+
24+
export function resolveRequireCallPath(options: {
25+
id: string;
26+
importer?: string;
27+
rootDir: string;
28+
conditionNames: string[];
29+
}) {
30+
if (RESOLVE_SKIP_RE.test(options.id) || BUILTIN_MODULE_SET.has(options.id)) {
31+
return;
32+
}
33+
return resolveModulePath(options.id, {
34+
try: true,
35+
from: options.importer && isAbsolute(options.importer) ? options.importer : options.rootDir,
36+
conditions: options.conditionNames,
37+
});
38+
}
39+
40+
export function requireConditionResolver(nitro: Nitro): RolldownPlugin {
41+
const conditionNames = getRequireConditionNames(nitro.options.exportConditions || []);
42+
43+
return {
44+
name: "nitro:rolldown-require-conditions",
45+
resolveId: {
46+
order: "pre",
47+
handler(source, importer, resolveOptions) {
48+
if (resolveOptions.kind !== "require-call") {
49+
return null;
50+
}
51+
const resolved = resolveRequireCallPath({
52+
id: source,
53+
importer,
54+
rootDir: nitro.options.rootDir,
55+
conditionNames,
56+
});
57+
if (!resolved) {
58+
return null;
59+
}
60+
return { id: resolved };
61+
},
62+
},
63+
};
64+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { afterAll, describe, expect, it } from "vitest";
2+
import { join } from "pathe";
3+
import { tmpdir } from "node:os";
4+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
5+
import {
6+
getRequireConditionNames,
7+
resolveRequireCallPath,
8+
} from "../../src/build/rolldown/require-condition.ts";
9+
10+
describe("resolveRequireCallPath", () => {
11+
const tmpDirs: string[] = [];
12+
13+
afterAll(async () => {
14+
await Promise.all(tmpDirs.map((dir) => rm(dir, { recursive: true, force: true })));
15+
});
16+
17+
it("prefers require condition for require-call package resolution", async () => {
18+
const rootDir = await mkdtemp(join(tmpdir(), "nitro-rolldown-require-condition-"));
19+
tmpDirs.push(rootDir);
20+
21+
const pkgDir = join(rootDir, "node_modules/dual-entry");
22+
await mkdir(join(pkgDir, "esm"), { recursive: true });
23+
await mkdir(join(pkgDir, "cjs"), { recursive: true });
24+
await writeFile(
25+
join(pkgDir, "package.json"),
26+
JSON.stringify(
27+
{
28+
name: "dual-entry",
29+
version: "0.0.0",
30+
exports: {
31+
".": {
32+
import: "./esm/index.mjs",
33+
require: "./cjs/index.cjs",
34+
default: "./cjs/index.cjs",
35+
},
36+
},
37+
},
38+
null,
39+
2
40+
)
41+
);
42+
await writeFile(join(pkgDir, "esm/index.mjs"), "export default { entry: 'esm' };\n");
43+
await writeFile(join(pkgDir, "cjs/index.cjs"), "module.exports = class DualEntry {};\n");
44+
45+
const resolved = resolveRequireCallPath({
46+
id: "dual-entry",
47+
rootDir,
48+
conditionNames: getRequireConditionNames(["workerd", "import", "default"]),
49+
});
50+
51+
expect(resolved?.replaceAll("\\", "/")).toMatch(/dual-entry\/cjs\/index\.cjs$/);
52+
});
53+
54+
it("skips builtins and relative imports", () => {
55+
const conditionNames = getRequireConditionNames(["workerd", "import", "default"]);
56+
const rootDir = process.cwd();
57+
expect(resolveRequireCallPath({ id: "events", rootDir, conditionNames })).toBeUndefined();
58+
expect(resolveRequireCallPath({ id: "./local", rootDir, conditionNames })).toBeUndefined();
59+
});
60+
});

0 commit comments

Comments
 (0)