Skip to content

feat!: convert the package to ESM only - #323

Open
cryptodev-2s wants to merge 2 commits into
migrate/pr2h-fix-tsdfrom
migrate/pr2i-esm-only
Open

feat!: convert the package to ESM only#323
cryptodev-2s wants to merge 2 commits into
migrate/pr2h-fix-tsdfrom
migrate/pr2i-esm-only

Conversation

@cryptodev-2s

@cryptodev-2s cryptodev-2s commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Top of stack #315, on #322.

Core is moving to an ESM only monorepo (MetaMask/core#9536), so this package should arrive already converted rather than landing as the only hybrid one in it.

Breaking

require('@metamask/utils') now fails with ERR_REQUIRE_ESM. main and module are gone, and both . and ./node resolve through exports to a single ./dist/*.js with ./dist/*.d.ts types. Consumers already using import are unaffected.

Before After
build ts-bridge tsc
output .cjs + .mjs + .d.cts + .d.mts .js + .d.ts
@ts-bridge/cli yes removed (rimraf added for cleaning)

ts-bridge exists to emit both formats, so it goes with the CJS half.

Import specifiers

102 relative specifiers across 45 files gained explicit .js extensions, which ESM requires; directories resolve to /index.js. Core's sources already look like this and enforce it with n/file-extension-in-import, so the same three import rules are adopted here verbatim.

Two things only the built output revealed

Neither would have been caught by the test suite, because tests run against src/:

  • lodashimport { memoize } from 'lodash' throws at runtime under ESM: Node's lexer cannot see named exports through lodash's CJS. Switched to lodash/memoize.js, a default import of the single method. Core hit the same wall and solved it with lodash-es plus a jest moduleNameMapper; this approach needs neither.
  • @metamask/scure-bip39 — the deep wordlist import needed an explicit .js.

Every other CJS dependency survives named imports untouched. semver, @metamask/superstruct, @scure/base, @noble/hashes and pony-cause all have lexer friendly CJS. Verified by importing all 27 built modules individually, plus the exports map by bare specifier.

Tooling

jest.config.js and .prettierrc.js are renamed to .cjs, since "type": "module" makes bare .js files ESM. Tests still compile to CommonJS via a ts-jest transform override (matching core) with a moduleNameMapper stripping the .js specifiers back off. constraints.pro is rewritten for the single entrypoint shape.


Note

High Risk
Breaking module format and entrypoint resolution affects every consumer still on CommonJS require; lodash and deep-import path changes can break at runtime if missed in downstream bundles.

Overview
Breaking: The published package is now ESM-only. CommonJS output and main / module are removed; require('@metamask/utils') fails with ERR_REQUIRE_ESM. Entry points . and ./node resolve through exports to a single ./dist/*.js with matching ./dist/*.d.ts. Consumers already using import are unaffected.

The build pipeline switches from ts-bridge (dual CJS/ESM) to tsc, with rimraf for dist cleanup and constraints.pro / CHANGELOG updated for the new package shape.

Relative imports across src gain explicit .js specifiers (plus ESLint n/file-extension-in-import). Jest keeps running tests as CommonJS via ts-jest overrides and a moduleNameMapper that strips .js back to .ts sources.

Runtime fixes for ESM output: hex.ts uses lodash/memoize.js (default import) instead of a named lodash import, and the BIP-39 wordlist deep import adds .js.

Reviewed by Cursor Bugbot for commit a2d5883. Bugbot is set up for automated code reviews on this repo. Configure here.

@socket-security

socket-security Bot commented Sep 4, 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
Addedrimraf@​5.0.109910010083100

View full report

@socket-security

socket-security Bot commented Sep 4, 2026

Copy link
Copy Markdown

Caution

MetaMask internal reviewing guidelines:

  • Do not ignore-all
  • Each alert has instructions on how to review if you don't know what it means. If lost, ask your Security Liaison or the supply-chain group
  • Copy-paste ignore lines for specific packages or a group of one kind with a note on what research you did to deem it safe.
    @SocketSecurity ignore npm/PACKAGE@VERSION
Action Severity Alert  (click "▶" to expand/collapse)
Block High
Obfuscated code: npm rimraf is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package.jsonnpm/rimraf@5.0.10

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rimraf@5.0.10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Environment variable access: npm rimraf reads __TESTING_RIMRAF_PLATFORM__

Env Vars: TESTING_RIMRAF_PLATFORM

Location: Package overview

From: package.jsonnpm/rimraf@5.0.10

ℹ Read more on: This package | This alert | What is environment variable access?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should be clear about which environment variables they access, and care should be taken to ensure they only access environment variables they claim to.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rimraf@5.0.10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Low
Environment variable access: npm rimraf

Env Vars: TESTING_RIMRAF_NODE_VERSION

Location: Package overview

From: package.jsonnpm/rimraf@5.0.10

ℹ Read more on: This package | This alert | What is environment variable access?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should be clear about which environment variables they access, and care should be taken to ensure they only access environment variables they claim to.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rimraf@5.0.10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Core is moving to an ESM only monorepo (MetaMask/core#9536), so this package
should arrive already converted rather than landing as the sole hybrid one.

BREAKING: the CommonJS build is gone. require('@metamask/utils') now fails
with ERR_REQUIRE_ESM. `main` and `module` are removed, and both `.` and
`./node` resolve through `exports` to a single ./dist/*.js with ./dist/*.d.ts
types. Consumers already using `import` are unaffected.

  package.json    adds "type": "module", collapses the dual exports map
  build           ts-bridge -> tsc, since ts-bridge exists to emit both formats
  @ts-bridge/cli  removed, rimraf added for build:only-clean
  tsconfig.build  drops emitDeclarationOnly, tsc now emits the JS too

102 relative import specifiers across 45 files gained explicit .js
extensions, which ESM requires. Directories resolve to /index.js. Core's
sources already look like this and enforce it with n/file-extension-in-import,
so the same three import rules are adopted here verbatim.

Two things only surfaced by running the built output rather than the tests:

  lodash  `import { memoize } from 'lodash'` throws at runtime under ESM,
          because Node's lexer cannot see named exports through lodash's CJS.
          Switched to `lodash/memoize.js`, a default import of the single
          method. Core solved the same problem by moving to lodash-es plus a
          jest moduleNameMapper; this needs neither.

  scure-bip39  the deep wordlist import needed an explicit .js.

Every other CJS dependency survives named imports untouched: semver,
superstruct, @scure/base, @noble/hashes and pony-cause all have lexer
friendly CJS. Verified by importing all 27 built modules individually.

jest.config.js and .prettierrc.js are renamed to .cjs, since "type": "module"
makes bare .js ESM. Tests still compile to CommonJS through a ts-jest
transform override, matching core, with a moduleNameMapper stripping the .js
specifiers back off. constraints.pro is rewritten for the single entrypoint
shape.
test:source runs `jest && jest-it-up`, and jest-it-up defaults to looking
for jest.config.js, which is now jest.config.cjs. It supports --config, so
point it there.

Caught by CI rather than locally: I had been running `yarn jest` directly to
work around a broken watchman on this machine, which skipped jest-it-up
entirely, so test:source was never actually exercised.
@cryptodev-2s
cryptodev-2s force-pushed the migrate/pr2i-esm-only branch 2 times, most recently from 0504ee1 to a2d5883 Compare September 7, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant