Skip to content

TypeScript: Generate CommonJS module types with flow-api-translator 0.333 - #1996

Open
robhogan wants to merge 1 commit into
pr1997from
pr1996
Open

robhogan wants to merge 1 commit into
pr1997from
pr1996

Conversation

@robhogan

@robhogan robhogan commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

flow-api-translator 0.332.0 translates declare module.exports to TypeScript's export = (facebook/flow#9491). Until now our .d.ts generator rejected any module that used it, which is why nearly all of metro-runtime was excluded, and why most of our hand-written .d.ts overrides exist.

With the flow-* packages on 0.333.0, this generates metro-runtime like any other package, and removes eight overrides the translator now replaces - metro-minify-terser, metro-symbolicate and six in metro-file-map (one of which had no .js beside it, so was never used).

Getting there took a few generator changes, and some light-touch changes to Flow source that don't affect runtime:

  • Flow comment types (/*: T */, /*:: ... */) are expanded before translation, because the metro-file-map workers run in Node without Babel. It's a regex, but it seems good enough - flow-parser's WASM build drops Flotate.
  • An adjacent .js.flow is used as the Flow source for an untyped .js, per Flow's own convention. That covers the vendored eventemitter3.
  • asyncRequire is exported through an as AsyncRequire cast, otherwise translation loses prefetch and unstable_importMaybeSync.

Lint on generated .d.ts is relaxed too:

  • no-unused-vars reports a declaration that a CommonJS module exports via typeof. It's possible to write Flow that avoids it, but that shouldn't be a concern for Flow authors.
  • no-explicit-any - if one appears, it's because we've already suppressed the equivalent in Flow, so respect that.
  • no-empty-object-type is more legitimate. Flow's inexact {...} becomes {}, and a follow-up re-enables the rule with better types for require.js.

Snapshots for the new CommonJS entry points depend on #1988, which teaches API Extractor about export =.

Changelog:

 - **[Types]**: Add TypeScript definitions for `metro-runtime` modules, and generate (rather than hand-maintain) types for `metro-file-map` workers

Test plan:

  • Snapshots for ES module entry points are unchanged, so removing the overrides changes no public API.
  • Inspect the four new metro-runtime API.md files. Some have messy identifiers, but they effectively capture the public surface.
  • Generate .d.ts locally (they're gitignored) and inspect the output.

@robhogan
robhogan added this pull request to stack #1997 September 29, 2026 14:48
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 29, 2026
Base automatically changed from pr1988 to main September 29, 2026 15:09
….333

Summary:
`flow-api-translator` 0.332.0 translates `declare module.exports` to TypeScript's `export =` (facebook/flow#9491). Until now `scripts/generateTypeScriptDefinitions.js` rejected any module whose Flow def used it, which is why all of `metro-runtime` bar `modules/types.js` was excluded, and why most of our hand-maintained `src/**/*.d.ts` overrides exist.

With `flow-api-translator` on 0.333.0, this drops the `module.exports` rejection and the `metro-runtime` exclusion, and removes as many overrides as the translator can now replace:

 - `metro-runtime` is generated like any other package - `asyncRequire`, `HMRClient`, `null-module` and `polyfills/require` gain types and API snapshots.
 - Eight overrides go: `metro-minify-terser` and `metro-symbolicate` (generated output is equivalent), the five CommonJS worker and extractor modules in `metro-file-map`, and `metro-file-map/src/lib/dependencyExtractor.d.ts`, which had no `.js` beside it and so was never used.
 - Two stay: `metro-transform-plugins/src/index.d.ts`, because it combines `export type` with `module.exports`, which TypeScript can't express as `export =` without namespace merging; and `crawlers/watchman/planQuery.d.ts`, because the generated output imports types from `fb-watchman`, which has none.

Getting there needed a few changes to the generator and to Flow sources, none of which affect runtime behaviour:

 - Flow comment types (`/*: T */`, `/*:: ... */`) are expanded before translation. The `metro-file-map` workers are loaded in Node without Babel and are typed that way, so the translator saw no annotations at all and emitted `any`.
 - A `.js.flow` file is used as the Flow source for the adjacent `.js`. That's Flow's own convention, and it types the vendored `eventemitter3` (no `@flow` directive) without an override.
 - `lintText` returns no result for a path `.eslintignore` covers (`**/vendor/**`), so the generator tolerates that and leaves the output unlinted.
 - The two `metro-file-map` plugin workers now declare their class and export it by name, rather than exporting an anonymous class expression, which `flowToFlowDef` can't type.
 - `asyncRequire` exports through an `as AsyncRequire` cast - `flowToFlowDef` drops statics assigned to a function, so `prefetch` and `unstable_importMaybeSync` were otherwise lost. The cast is type-only, and `metro-runtime` already uses `as` casts elsewhere.
 - `HMRClient._heartbeatTimer` is typed `?ReturnType<typeof setInterval>` in place of Flow's `IntervalID` global, which the translator passes through and TypeScript has no name for.
 - Generated `.d.ts` no longer run `@typescript-eslint`'s `no-explicit-any` or `no-unused-vars`. Flow checks the sources these are translated from, and an `any` in the output reflects one there; the second rule reports a declaration a CommonJS module exports via `typeof` as unused, which is most of what `metro-runtime` and the `metro-file-map` workers now emit. `no-unused-vars` stays on in the generator's own `--fix` pass, which uses it to drop declarations left unused by translation. `no-empty-object-type` is off too, for now: Flow's inexact `{...}` translates to `{}`, which the rule flags in `polyfills/require.d.ts` - the follow-up diff fixes those annotations at source and turns the rule back on. This replaces the per-file exemption for `metro-file-map/types/flow-types.d.ts`.

Changelog:
```
 - **[Types]**: Add TypeScript definitions for `metro-runtime` modules, and generate (rather than hand-maintain) types for `metro-file-map` workers
```

Test plan:
```
yarn flow check
yarn typecheck-ts
yarn build-api-snapshots
yarn verify-api-snapshots
yarn jest
```

Flow and `tsc` are clean. API snapshots for ES module entry points are unchanged, so removing the overrides changes no public API. `metro-runtime` gains four snapshots, and `metro-minify-terser`'s now reflects its generated definition. Jest: all 150 suites pass, including the `metro` integration tests, which bundle `metro-runtime`.
@robhogan
robhogan removed this pull request from stack #1997 September 29, 2026 15:13
@robhogan robhogan changed the title TypeScript: Update to flow-api-translator 0.333, generate CommonJS module types TypeScript: Generate CommonJS module types with flow-api-translator 0.333 Sep 29, 2026
@robhogan
robhogan changed the base branch from main to pr1997 September 29, 2026 15:13
@robhogan
robhogan added this pull request to stack #1999 September 29, 2026 15:13

This branch has not been deployed

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant