fix: support ESM app entrypoints in the universal shim#210
Conversation
When the x64 and arm64 app.asar (or app dir) contents diverge, the
generated entry shim used `require()` to load the per-arch archive. If
the inner app's entrypoint is an ES module this throws ERR_REQUIRE_ESM
at launch.
Detect each arch's entrypoint module format from its package.json and,
only when both arches agree on ESM, ship an ESM shim that uses dynamic
import. If the two arches disagree on module system, fail the build with
a clear error instead of producing a broken bundle.
- Add detectEntrypointModule / resolveShimModule to asar-utils
- Add ESM shim sources entry-asar/esm/{has,no}-asar.mts compiled to .mjs
- Wire both the HAS_ASAR and NO_ASAR shim sites in makeUniversalApp
- Add unit tests for the detection logic and ESM integration tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPgSb3xj4vdi1mUNSzpVUD
The no-asar ESM fixtures are copied straight into the bundle tree, so diverging via a loose plain `extra-file.txt` was classified as a unique PLAIN file and tripped makeUniversalApp's mach-o parity guard. Diverge via uniquely-named `.bin` files instead (classified as V8 snapshots and excluded from the parity check), exactly the way the existing non-ESM `should shim two different app folders` test does.
The heavy verifyApp integration tests run ~60s each on their own and the suite is contended under maxConcurrency, so 80s left too little margin and one test timed out at ~140s. Raise VERIFY_APP_TIMEOUT to 180s.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Requested by Samuel Attard · Slack thread
Before / After
Before: building a universal binary from an Electron app whose main-process entrypoint is ESM (a
.mjsentry, or"type": "module") crashed at launch withERR_REQUIRE_ESMwhenever the x64 and arm64 ASARs diverged. The generated outerapp.asarshim doesrequire()of the per-arch ASAR, andrequire()can't load an ESM entrypoint. (#90)After: when both arches' entrypoints are ESM, the shim is emitted as an ESM module that uses dynamic
import()instead, so ESM apps launch correctly. CommonJS apps are unchanged.Safety guard
We only ship the ESM shim when we've positively confirmed the entrypoint's module format. Format is read from each ASAR's own
package.jsonusing Node's rules (.mjs/.cjsextension wins; otherwise the"type"field decides), and anything we can't positively identify as ESM is treated as CommonJS — so we never switch on a guess. Both arches must agree: if one arch is ESM and the other CommonJS, we throw a clear error rather than ship a binary that would crash on one of them.How
src/asar-utils.ts: newdetectEntrypointModule(packageJson)andresolveShimModule(x64, arm64)helpers.src/index.ts: both shim copy sites now read each arch'spackage.json(NO_ASAR from disk, HAS_ASAR viaasar.extractFile), resolve the module, and on a confirmed ESM verdict copyentry-asar/esm/{has,no}-asar.mjs→index.mjsand setpj.main = 'index.mjs'; otherwise the existing.js/index.jspath is unchanged.entry-asar/esm/usecreateRequire+ top-levelawait import(pathToFileURL(process._archPath).href), compiled by a dedicatedentry-asar/esm/tsconfig.json(nodenext/es2022) added as a thirdtscbuild pass.Tests
test/detect-entrypoint.spec.ts: 16 unit tests covering the detection matrix and the divergence guard (written first, TDD).test/index.spec.ts(HAS_ASAR + NO_ASAR ESM shim) with ESM fixtures generated intest/globalSetup.ts. These exercise on macOS CI (require lipo/clang/arch).Closes #90.
Generated by Claude Code