Skip to content

fix: support ESM app entrypoints in the universal shim#210

Merged
erickzhao merged 10 commits into
mainfrom
fix/esm-asar-entrypoint
Jul 2, 2026
Merged

fix: support ESM app entrypoints in the universal shim#210
erickzhao merged 10 commits into
mainfrom
fix/esm-asar-entrypoint

Conversation

@claude

@claude claude Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Requested by Samuel Attard · Slack thread

Before / After

Before: building a universal binary from an Electron app whose main-process entrypoint is ESM (a .mjs entry, or "type": "module") crashed at launch with ERR_REQUIRE_ESM whenever the x64 and arm64 ASARs diverged. The generated outer app.asar shim does require() of the per-arch ASAR, and require() 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.json using Node's rules (.mjs/.cjs extension 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: new detectEntrypointModule(packageJson) and resolveShimModule(x64, arm64) helpers.
  • src/index.ts: both shim copy sites now read each arch's package.json (NO_ASAR from disk, HAS_ASAR via asar.extractFile), resolve the module, and on a confirmed ESM verdict copy entry-asar/esm/{has,no}-asar.mjsindex.mjs and set pj.main = 'index.mjs'; otherwise the existing .js/index.js path is unchanged.
  • New ESM shims in entry-asar/esm/ use createRequire + top-level await import(pathToFileURL(process._archPath).href), compiled by a dedicated entry-asar/esm/tsconfig.json (nodenext/es2022) added as a third tsc build pass.

Tests

  • New test/detect-entrypoint.spec.ts: 16 unit tests covering the detection matrix and the divergence guard (written first, TDD).
  • New integration tests in test/index.spec.ts (HAS_ASAR + NO_ASAR ESM shim) with ESM fixtures generated in test/globalSetup.ts. These exercise on macOS CI (require lipo/clang/arch).

Closes #90.


Generated by Claude Code

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
@MarshallOfSound MarshallOfSound marked this pull request as ready for review June 28, 2026 17:32
@MarshallOfSound MarshallOfSound requested a review from a team as a code owner June 28, 2026 17:32
claude Bot added 6 commits June 28, 2026 17:43
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.
@socket-security

socket-security Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedelectron@​43.0.09910010098100

View full report

@socket-security

socket-security Bot commented Jun 28, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

erickzhao and others added 2 commits July 2, 2026 16:28
Bump the aliased electron dev dependency used by the ESM fixtures from
electron@^28.0.0 to the latest stable electron@^43.0.0 and rename the
alias/constant accordingly (electron28 -> electron43,
ELECTRON_28_VERSION -> ELECTRON_43_VERSION).
@erickzhao erickzhao merged commit 05b9170 into main Jul 2, 2026
6 checks passed
@erickzhao erickzhao deleted the fix/esm-asar-entrypoint branch July 2, 2026 21:08
@electron-npm-package-publisher

Copy link
Copy Markdown

🎉 This PR is included in version 3.0.6 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App entrypoint fails to load for ESM app if arm64 and x64 apps don't match

4 participants