Skip to content

feat!: replace hybrid CJS/ESM build with ESM-only#9536

Draft
Mrtenz wants to merge 136 commits into
esmfrom
mrtenz/esm
Draft

feat!: replace hybrid CJS/ESM build with ESM-only#9536
Mrtenz wants to merge 136 commits into
esmfrom
mrtenz/esm

Conversation

@Mrtenz

@Mrtenz Mrtenz commented Jul 16, 2026

Copy link
Copy Markdown
Member

Explanation

Currently, packages in this monorepo are built as both CommonJS and ESM using @ts-bridge/cli. This PR replaces that with ESM-only output built with standard tsc and TypeScript project references.

The motivation is to simplify the build toolchain by removing the ts-bridge dependency in preparation for the TypeScript 6+ upgrade.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

@Mrtenz Mrtenz changed the title feat: replace hybrid CJS/ESM build with ESM-only feat!: replace hybrid CJS/ESM build with ESM-only Jul 16, 2026
@Mrtenz Mrtenz changed the base branch from main to esm July 16, 2026 17:14
Mrtenz added 9 commits July 16, 2026 19:15
Switch from `@ts-bridge/cli` to standard `tsc` with TypeScript project
references for building all packages.
Remove CJS entry points (`main`, `types`, `require`) from all package
manifests and simplify `exports` to a single `import` condition. This
drops the dual CJS/ESM output in favour of ESM-only distribution.
Mark all packages (and the root) as native ESM by adding
`"type": "module"` to their manifests. Also adds a Yarn constraint
to enforce this field going forward.
Use `n/file-extension-in-import` to require `.js` extensions on
relative imports (since `import-x/extensions` can't enforce this for
TypeScript files).
`__dirname` is not available in ESM. Replace all usages with
`import.meta.dirname`, which is equivalent and available since
Node.js 21.2.
Switch the shared and all per-package Jest configs from CJS
(`require`/`module.exports`) to ESM (`import`/`export default`).
Older versions of TypeScript break when using named imports in ESM.
Import the default export and destructure instead as a temporary
workaround. Also passes `--formatter oxfmt` to the
`messenger-action-types` scripts in packages missing it.
Replace leftover `__dirname` with `import.meta.dirname`.
@Mrtenz Mrtenz force-pushed the mrtenz/esm branch 2 times, most recently from 1ac08ca to 5e3bccb Compare July 16, 2026 17:15
The mapping sent all `uuid` imports to the ESM wrapper (`wrapper.mjs`),
which broke CJS `require('uuid')` calls made by external packages loaded
via Jest's CJS runtime. With `customExportConditions: ['node']` already
set, Jest resolves the correct `require`/`import` export for uuid
automatically without a manual mapping.
Add `.js` extensions to relative imports.
@socket-security

socket-security Bot commented Jul 16, 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
Added@​jest/​globals@​30.4.11001006388100
Added@​types/​lodash-es@​4.17.121001007780100

View full report

@socket-security

socket-security Bot commented Jul 16, 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 Medium
Potential security risk (AI signal): npm @jest/snapshot-utils is 68.0% likely risky

Notes: No overt malware indicators (no network, credentials, or obvious sabotage) are present. However, this module contains a high-impact file-to-code execution sink: it executes snapshot file contents using new Function when loading snapshots. If snapshot files or snapshotPath are attacker-influenced (e.g., supply-chain tampering, repo compromise, CI artifact injection), this can lead to arbitrary JavaScript execution in the test/runtime context. Otherwise, the remaining logic is primarily formatting/serialization and filesystem writes with minimal additional risk.

Confidence: 0.68

Severity: 0.70

From: ?npm/@jest/globals@30.4.1npm/@jest/snapshot-utils@30.4.1

ℹ Read more on: This package | This alert | What are AI-detected potential security risks?

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: An AI system identified potential security problems in this package. It is advised to review the package thoroughly and assess the potential risks before installation. You may also consider reporting the issue to the package maintainer or seeking alternative solutions with a stronger security posture.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@jest/snapshot-utils@30.4.1. 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.

Block Low
Publisher changed: npm @jest/diff-sequences is now published by simenb instead of cpojer

New Author: simenb

Previous Author: cpojer

From: ?npm/@jest/globals@30.4.1npm/@jest/diff-sequences@30.4.0

ℹ Read more on: This package | This alert | What is new author?

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: Scrutinize new collaborator additions to packages because they now have the ability to publish code into your dependency tree. Packages should avoid frequent or unnecessary additions or changes to publishing rights.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@jest/diff-sequences@30.4.0. 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

Mrtenz added 3 commits July 16, 2026 23:23
Add `.js` extensions to relative imports. Replace `lodash` with
`lodash-es` for ESM-compatible named exports.
Add `.js` extensions to relative imports.
Add `.js` extensions to relative imports.
@Mrtenz Mrtenz force-pushed the mrtenz/esm branch 3 times, most recently from 4f649cb to 3d0e767 Compare July 16, 2026 22:19
Mrtenz added 3 commits July 17, 2026 00:27
Add `.js` extensions to relative imports. Suppress pre-existing
`no-misused-promises` errors where async middleware callbacks are
passed to void-typed parameters.
Add `.js` extensions to relative imports.
Mrtenz added 29 commits July 17, 2026 00:27
Several ESLint rule conflicts and gaps arose from the ESM-only migration:

- Allow `jest` to shadow its global in test files: importing `jest` from
  `@jest/globals` (required in ESM) shadows the Jest-injected global, so
  `@typescript-eslint/no-shadow` is set to allow `['jest']` in test files.
- Allow `void expr` as a statement: `no-floating-promises` requires `void`
  to mark intentionally ignored promises, but `no-void` was blocking it;
  `allowAsStatement: true` resolves the conflict.
- Turn off `import-x/unambiguous` for CJS config files (`*.cjs`,
  `jest.environment.js`, `.eslintrc.js`, etc.) that have no import/export.
- Add `json: 'always'` to `import-x/extensions` so JSON imports are not
  flagged as using an unexpected file extension.
- Fix `jest.resolver.cjs`: formatter moved inline `eslint-disable-line`
  comments to the next line, breaking them; converted to `eslint-disable-next-line`.
- Add `export {}` to `tests/setup.ts` so it is unambiguously an ES module.
- Add `@jest/globals: ^30.4.1` to root package.json devDependencies so the
  package is available for top-level test infrastructure.
- Remove `prefer-nullish-coalescing` suppression entries that are no longer
  needed now that those errors were fixed in the source files.
- Update yarn.lock to reflect the new and updated dependencies.
TypeScript with `moduleResolution: "Node16"` determines whether a `.d.ts`
file is ESM or CJS based on the package's `"type"` field.
`@metamask/safe-event-emitter` has no `"type": "module"`, so TypeScript
treats its ESM type declarations (`dist/esm/index.d.ts`) as CJS, causing
the default import to resolve as the module namespace rather than the
class constructor.

The fix: add a `dist/esm/index.d.mts` file (which TypeScript always
treats as ESM, regardless of `"type"`) and point the `"import"` condition
in the package's `exports` field at it.
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