diff --git a/packages/server/src/server/plugins/compiler.test.ts b/packages/server/src/server/plugins/compiler.test.ts index dca7d3e3e2..facba48fdb 100644 --- a/packages/server/src/server/plugins/compiler.test.ts +++ b/packages/server/src/server/plugins/compiler.test.ts @@ -455,6 +455,33 @@ export type Value = string;`, expect(serverBundle).not.toContain("Client contribution"); }); + it.each(["main", "module"])( + "builds a dependency that declares only %s into both runtimes", + async (field) => { + const entries = await createSplitPlugin(); + const dependency = path.join(entries.directory, "node_modules/legacy-entry"); + await mkdir(dependency, { recursive: true }); + await writeFile( + path.join(dependency, "package.json"), + JSON.stringify({ name: "legacy-entry", [field]: "lib/entry.js" }), + ); + await mkdir(path.join(dependency, "lib")); + await writeFile( + path.join(dependency, "lib/entry.js"), + 'exports.label = "legacy entry contribution";', + ); + await writeFile( + path.join(entries.directory, "shared/labels.ts"), + 'import { label } from "legacy-entry"; export const clientLabel = label; export const serverLabel = label;', + ); + + const { clientBundle, serverBundle } = await compilePlugin(entries); + + expect(clientBundle).toContain("legacy entry contribution"); + expect(serverBundle).toContain("legacy entry contribution"); + }, + ); + it("uses the automatic JSX runtime without a React import", async () => { const entries = await createSplitPlugin(); const { clientBundle } = await compilePlugin(entries); diff --git a/packages/server/src/server/plugins/compiler.ts b/packages/server/src/server/plugins/compiler.ts index 80dbb1b7b0..15df395d06 100644 --- a/packages/server/src/server/plugins/compiler.ts +++ b/packages/server/src/server/plugins/compiler.ts @@ -391,6 +391,10 @@ async function compileTarget(entryPath: string, target: PluginBuildTarget): Prom jsx: "automatic", platform: target === "server" ? "node" : "neutral", target: target === "server" ? "node20" : "es2020", + // The neutral platform reads no package.json entry fields, so packages without + // `exports` would not resolve. Read the same fields as esbuild's browser platform + // minus `browser`, since the bundle also runs in React Native. + mainFields: target === "client" ? ["module", "main"] : undefined, // Metro lowers async syntax before Hermes sees app code. Plugin client bundles bypass Metro, // so apply the same compatibility transform before the app evaluates them from source. supported: target === "client" ? { "async-await": false } : undefined,