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.
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 withERR_REQUIRE_ESM.mainandmoduleare gone, and both.and./noderesolve throughexportsto a single./dist/*.jswith./dist/*.d.tstypes. Consumers already usingimportare unaffected.ts-bridgetsc.cjs+.mjs+.d.cts+.d.mts.js+.d.ts@ts-bridge/clirimrafadded for cleaning)ts-bridgeexists to emit both formats, so it goes with the CJS half.Import specifiers
102 relative specifiers across 45 files gained explicit
.jsextensions, which ESM requires; directories resolve to/index.js. Core's sources already look like this and enforce it withn/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/:lodash—import { memoize } from 'lodash'throws at runtime under ESM: Node's lexer cannot see named exports through lodash's CJS. Switched tolodash/memoize.js, a default import of the single method. Core hit the same wall and solved it withlodash-esplus a jestmoduleNameMapper; 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/hashesandpony-causeall have lexer friendly CJS. Verified by importing all 27 built modules individually, plus the exports map by bare specifier.Tooling
jest.config.jsand.prettierrc.jsare renamed to.cjs, since"type": "module"makes bare.jsfiles ESM. Tests still compile to CommonJS via a ts-jesttransformoverride (matching core) with amoduleNameMapperstripping the.jsspecifiers back off.constraints.prois rewritten for the single entrypoint shape.Note
High Risk
Major breaking changes for all consumers on CommonJS/require or Node 18/20, plus a new published module format and build pipeline that must resolve correctly at runtime.
Overview
This PR makes @metamask/utils ESM-only and aligns it with MetaMask core:
require('@metamask/utils')fails;main/moduleand dual CJS+ESM builds are removed in favor of a singleexports-based./dist/*.js+./dist/*.d.tslayout for.and./node.Build and packaging:
ts-bridgeis replaced bytsc, withrimraffor clean builds."type": "module"is set;constraints.proenforces the new export shape andengines.node:^22.14.0 || ^24. CI matrices drop Node 18/20 and test on 22.x and 24.x.Source and runtime fixes for ESM: Relative imports across
src/use explicit.jsspecifiers; ESLint addsn/file-extension-in-import(matching core). Jest keeps running tests as CJS viamoduleNameMapperand a ts-jest CommonJS override. Runtime import fixes includelodash/memoize.js(default import) and an explicit.json the scure-bip39 wordlist deep import.The changelog also records breaking Node support removal and dependency bumps already noted in the stack (e.g.
@ethereumjs/txv5).Reviewed by Cursor Bugbot for commit 0067215. Bugbot is set up for automated code reviews on this repo. Configure here.