#3412

island: ESM module with 'module.exports' named export loses its default export across the boundary

Openespetroopened 4d ago

Summary

An ESM module running in a Node island that contains export {x as 'module.exports'} — an export named the string module.exports — resolves to undefined on the native side's default import. import shout from "pkg" gives undefined; calling it throws TypeError: value is not a function. This is what breaks yargs under islands: yargs/index.mjs ends with export {Yargs as 'module.exports'}.

Reproduction

SCRIPTC_LIBNODE=<pack> scriptc build main.mts -o app --node-island=islpkg && ./app
// node_modules/islpkg/index.mjs
function factory(shim) { return function shout(s) { return shim(s).toUpperCase(); }; }
const shim = (s) => s;
const shout = factory(shim);
export default shout;
export {shout as 'module.exports'};   // <-- this line breaks it

// main.mts
import shout from "islpkg";
console.log("default:", typeof shout, shout("hi"));
  • Without the 'module.exports' export: default: function HI ✅
  • With it: Uncaught TypeError: value is not a function ❌
  • --node-islands=off (fully native): works ✅ — island-boundary specific.

Verified on main (d4c82a5c3), darwin-arm64, libnode pack built per packages/libnode-common/build-pack.mjs.

Likely location

packages/runtime/src/scr_node_island_host.cjs (namespace[name] reads) and/or packages/compiler/src/frontend/lowering/lower-node-island.ts — the presence of a 'module.exports' export name seems to make the boundary treat the ESM module as CJS-facade-shaped, reading the wrong slot for default.

A PR is underway.

Activity

0

No activity yet.