Skip to content

Commit 1c815de

Browse files
authored
fix: detect object-valued conditions (#1243)
Co-Authored-By: Claude Opus 4.7
1 parent 3489d38 commit 1c815de

3 files changed

Lines changed: 79 additions & 18 deletions

File tree

.changeset/stupid-ways-rush.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
fix: detect object-valued conditions
6+
7+
The pre-existing build condition transform logic had subtle errors:
8+
9+
- failed to recognize object conditions
10+
(e.g. "workerd": { "import": ..., "require": ... })
11+
- sibling pruning only applied to strings, not objects
12+
13+
Now, we fully support object conditions. Furthermore, we prune siblings,
14+
unless its subtree also contains the build condition.

packages/cloudflare/src/cli/build/utils/workerd.spec.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@ describe("transformBuildCondition", () => {
5959
expect(defaultExport.transformedExports).toEqual({
6060
".": "/path/to/index.js",
6161
"./server": {
62-
"react-server": {
63-
workerd: "./server.edge.js",
64-
other: "./server.js",
65-
},
6662
default: "./server.js",
6763
},
6864
});
@@ -80,7 +76,7 @@ describe("transformBuildCondition", () => {
8076
});
8177
});
8278

83-
test("only consider leaves", () => {
79+
test("object-valued condition", () => {
8480
const exports = {
8581
".": "/path/to/index.js",
8682
"./server": {
@@ -92,7 +88,7 @@ describe("transformBuildCondition", () => {
9288

9389
const workerd = transformBuildCondition(exports, "workerd");
9490

95-
expect(workerd.hasBuildCondition).toBe(false);
91+
expect(workerd.hasBuildCondition).toBe(true);
9692
expect(workerd.transformedExports).toEqual({
9793
".": "/path/to/index.js",
9894
"./server": {
@@ -102,6 +98,25 @@ describe("transformBuildCondition", () => {
10298
},
10399
});
104100
});
101+
102+
test("preserve sibling subtree that nests the condition", () => {
103+
const exports = {
104+
"react-server": {
105+
workerd: "./rsc.edge.js",
106+
},
107+
workerd: "./top.edge.js",
108+
};
109+
110+
const workerd = transformBuildCondition(exports, "workerd");
111+
112+
expect(workerd.hasBuildCondition).toBe(true);
113+
expect(workerd.transformedExports).toEqual({
114+
"react-server": {
115+
workerd: "./rsc.edge.js",
116+
},
117+
workerd: "./top.edge.js",
118+
});
119+
});
105120
});
106121

107122
describe("transformPackageJson", () => {
@@ -176,6 +191,38 @@ describe("transformPackageJson", () => {
176191
expect(hasBuildCondition).toBe(true);
177192
});
178193

194+
// https://github.com/opennextjs/opennextjs-cloudflare/issues/1153
195+
// Matches the exports field of pg-cloudflare@1.3.0.
196+
test("exports with object-valued workerd condition (pg-cloudflare)", () => {
197+
const json = {
198+
name: "pg-cloudflare",
199+
exports: {
200+
".": {
201+
workerd: {
202+
import: "./esm/index.mjs",
203+
require: "./dist/index.js",
204+
},
205+
default: "./dist/empty.js",
206+
},
207+
"./package.json": "./package.json",
208+
},
209+
};
210+
const { transformed, hasBuildCondition } = transformPackageJson(json);
211+
expect(transformed).toEqual({
212+
name: "pg-cloudflare",
213+
exports: {
214+
".": {
215+
workerd: {
216+
import: "./esm/index.mjs",
217+
require: "./dist/index.js",
218+
},
219+
},
220+
"./package.json": "./package.json",
221+
},
222+
});
223+
expect(hasBuildCondition).toBe(true);
224+
});
225+
179226
test("exports and imports with workerd condition both nested and top level", () => {
180227
const json = {
181228
name: "test",

packages/cloudflare/src/cli/build/utils/workerd.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,27 @@ export function transformBuildCondition(
2323
hasBuildCondition: boolean;
2424
} {
2525
const transformed: { [key: string]: unknown } = {};
26-
const hasTopLevelBuildCondition = Object.keys(conditionMap).some(
27-
(key) => key === condition && typeof conditionMap[key] === "string"
28-
);
26+
const hasTopLevelBuildCondition = condition in conditionMap && conditionMap[condition] != null;
2927
let hasBuildCondition = hasTopLevelBuildCondition;
3028
for (const [key, value] of Object.entries(conditionMap)) {
3129
if (typeof value === "object" && value != null) {
3230
const { transformedExports, hasBuildCondition: innerBuildCondition } = transformBuildCondition(
3331
value as { [key: string]: unknown },
3432
condition
3533
);
34+
35+
// If a build condition is present at this level but a sibling
36+
// subtree doesn't contain the build condition, we can drop it entirely.
37+
if (hasTopLevelBuildCondition && key !== condition && !innerBuildCondition) {
38+
continue;
39+
}
40+
3641
transformed[key] = transformedExports;
3742
hasBuildCondition ||= innerBuildCondition;
38-
} else {
39-
// If it doesn't have the build condition, we need to keep everything as is
40-
// If it has the build condition, we need to keep only the build condition
41-
// and remove everything else
42-
if (!hasTopLevelBuildCondition) {
43-
transformed[key] = value;
44-
} else if (key === condition) {
45-
transformed[key] = value;
46-
}
43+
} else if (!hasTopLevelBuildCondition || key === condition) {
44+
// If there is no build condition at this level or this is a non-object build condition,
45+
// we need to keep the child condition as is.
46+
transformed[key] = value;
4747
}
4848
}
4949
return { transformedExports: transformed, hasBuildCondition };

0 commit comments

Comments
 (0)