feat!: replace hybrid CJS/ESM build with ESM-only#9536
Conversation
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`.
1ac08ca to
5e3bccb
Compare
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.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Caution MetaMask internal reviewing guidelines:
|
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.
4f649cb to
3d0e767
Compare
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.
…est infrastructure
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.
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 standardtscand TypeScript project references.The motivation is to simplify the build toolchain by removing the
ts-bridgedependency in preparation for the TypeScript 6+ upgrade.References
Checklist