Let plugin client code import packages that declare only main (#5838)

The plugin compiler builds client code with esbuild's neutral platform,
which reads no package.json entry fields, so any dependency without an
exports map failed with Could not resolve. Read module and main for the
client target, as the server target already does through the node
platform.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-01 14:02:49 +02:00
committed by GitHub
parent 95774ba71f
commit e10f6d2e2c
2 changed files with 31 additions and 0 deletions
@@ -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);
@@ -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,