Skip to content

TypeScript: flow-api-translator to 0.333, auto-generate more TS defs - #1977

Closed
robhogan wants to merge 1 commit into
react:mainfrom
robhogan:flow-api-translator-0.333
Closed

robhogan wants to merge 1 commit into
react:mainfrom
robhogan:flow-api-translator-0.333

Conversation

@robhogan

Copy link
Copy Markdown
Collaborator

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.

This:

  • Bumps flow- libs to 0.333.0, matching flow-bin
  • Removes most of metro-runtime and other CJS modules from the generateApiSnapshots ignore list
  • Makes some light-touch changes to Flow source to allow TS translation - eg ambient IntervalID doesn't exist in TS, but we can use ReturnType<typeof setInverval>.
  • Improves the generator script:
    • Handle flow-comment syntax (with regex, but it seems good enough - flow-parser WASM drops Flotate)
    • Take an adjacent .js.flow of an untyped .js as the basis for .d.ts (vendored eventemitter3)
    • For API Extractor (API.md files) only, pretend export = is export default, otherwise it sees nothing.
  • Removes now-unnecessary manual .d.ts overrides.
  • Relaxes @typescript-eslint:
    • no-unused-vars - an artifact of flow-api-translator's output and although it's possible write Flow that works around it, it shouldn't be a concern for Flow authors.
    • no-explicit-any - if these appear, it's because we've already suppressed equivalents in Flow - respect that.
    • no-empty-object-type - this is more legitimate, it's re-enabled in a follow-up with better types for require.js

Changelog:

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

Test plan:
CI

  • Inspect generated API.md. Some have some messy identifiers, but they effectively capture the public surface.
  • Generate .d.ts locally (they're gitignored) and inspect the output

…dule types

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.

This diff bumps `flow-api-translator`, `flow-eslint` and `flow-parser` to 0.333.0, matching `flow-bin`, 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`.
 - `generateApiSnapshots.js` snapshots `export =` entry points properly. API Extractor ignores `export =` entirely (`ExportAnalyzer` skips `ts.InternalSymbolName.ExportEquals`), which is why `metro-minify-terser` and `metro-transform-plugins` had empty `API.md` files. For extraction only, those entry points now go through a temporary copy of their `.d.ts` that uses `export default`, exports its local declarations (in an `export =` module every type is local, and a report shows only exports), and imports other `export =` modules with `import x = require()` - a default import of one throws inside API Extractor. The published `.d.ts` keep `export =`. Separately, a report that still comes out empty is no longer written, stale ones are deleted, and `--verify` fails if one is checked in.

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-minify-terser` and `metro-transform-plugins` gain real snapshots in place of empty ones, and `metro-runtime` gains four. Jest: all 150 suites pass, including the `metro` integration tests, which bundle `metro-runtime`.
@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 26, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Sep 26, 2026
robhogan added a commit that referenced this pull request Sep 28, 2026
API Extractor ignores `export =`, so any entry point whose `.d.ts` uses it gets an empty API report. Currently that's `metro-minify-terser` and `metro-transform-plugins`, whose hand-written definitions both end in `export =`:

https://github.com/react/metro/blob/b7c20552a16a251507f753c3da23cc7d18488b6c/packages/metro-minify-terser/src/index.d.ts#L11-L14

Once #1977 generates definitions for CommonJS modules, it'll be all of those too.

This works around it for extraction only. API Extractor gets a temporary copy of the `.d.ts` that uses `export default` in place of `export =` and exports the module's local types, so they show up in the report. Published definitions keep `export =`. The copy is made by parsing the `.d.ts` with Babel and editing its text in place, so comments and formatting carry through to the report.

An entry point with nothing to report now gets no `API.md` rather than an empty one, and `--verify` fails on a stale one.

Changelog: Internal

Test plan:
`yarn run build-api-snapshots` fills in the `metro-minify-terser` and `metro-transform-plugins` reports and leaves the other 12 unchanged. The new `metro-runtime` reports in #1977 come from the same change.
@robhogan

Copy link
Copy Markdown
Collaborator Author

Superseded by #1996

@robhogan robhogan closed this Sep 29, 2026
robhogan added a commit that referenced this pull request Sep 29, 2026
API Extractor ignores `export =`, so any entry point whose `.d.ts` uses it gets an empty API report. Currently that's `metro-minify-terser` and `metro-transform-plugins`, whose hand-written definitions both end in `export =`:

https://github.com/react/metro/blob/b7c20552a16a251507f753c3da23cc7d18488b6c/packages/metro-minify-terser/src/index.d.ts#L11-L14

Once #1977 generates definitions for CommonJS modules, it'll be all of those too.

This works around it for extraction only. API Extractor gets a temporary copy of the `.d.ts` that uses `export default` in place of `export =` and exports the module's local types, so they show up in the report. Published definitions keep `export =`. The copy is made by parsing the `.d.ts` with Babel and editing its text in place, so comments and formatting carry through to the report.

An entry point with nothing to report now gets no `API.md` rather than an empty one, and `--verify` fails on a stale one.

Changelog: Internal

Test plan:
`yarn run build-api-snapshots` fills in the `metro-minify-terser` and `metro-transform-plugins` reports and leaves the other 12 unchanged. The new `metro-runtime` reports in #1977 come from the same change.
@robhogan
robhogan deleted the flow-api-translator-0.333 branch September 30, 2026 09:05
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. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant