Skip to content

Minify: Use the minifier's decoded source map where available - #1984

Draft
robhogan wants to merge 1 commit into
mainfrom
pr1984
Draft

robhogan wants to merge 1 commit into
mainfrom
pr1984

Conversation

@robhogan

@robhogan robhogan commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

When minifying, the transform worker converted Babel's decoded map to tuples and encoded it for the minifier, then decoded the minifier's output map with source-map (toBabelSegments), converted it back to tuples and encoded it again as the stored VLQ map. Terser already builds that decoded map, and only encodes it when result.map is read.

This diff:

  • Adds an optional decodedMap to MinifierResult. When a minifier provides it, the worker encodes the stored map straight from it with vlqMapFromBabelDecodedMap, as it already does for unminified modules. Otherwise it decodes map with @jridgewell/sourcemap-codec, which is already installed via @babel/generator.
  • Has metro-minify-terser return Terser's decoded_map as decodedMap, and encode map only if it's read.
  • Passes Babel's own encoded result.map to the minifier, rather than re-encoding it from tuples.
  • Deprecates MinifierResult.map in favour of decodedMap, and toBabelSegments, toSegmentTuple and tuplesFromBabelDecodedMap in metro-source-map, which Metro no longer uses. The next diff removes them.

Output is unchanged - across every module of our benchmark app*, the minified code, line count and stored map are byte-identical. Custom minifiers that only return map keep working as before.

Minifying the whole app is 10.4% faster (95% CI 7.4-11.4%; 21.3s -> 19.2s of CPU on our benchmark app*), from no longer converting the map to and from tuples, and from Terser no longer encoding its map at all:

Minify step (median) vs main
main 21,286ms (20,844 to 21,561)
This diff 19,228ms (18,886 to 19,773) -10.4% (-11.4 to -7.4)

Changelog

 - **[Performance]**: Faster source map handling when minifying, using the minifier's decoded map if it provides one (`MinifierResult.decodedMap`)
 - **[Deprecated]**: Custom minifiers should return their source map as `decodedMap` - `MinifierResult.map` is deprecated
 - **[Deprecated]**: `metro-source-map`: Deprecate `toBabelSegments`, `toSegmentTuple` and `tuplesFromBabelDecodedMap`

Test plan

New tests cover the worker using decodedMap without reading map, and metro-minify-terser returning Terser's decoded map and encoding map only on request.

yarn jest
yarn flow check
yarn lint
yarn verify-api-snapshots

* Benchmark: Mattermost Mobile 2.45.0 (React Native 0.83.9), its release bundle split back into its 7,082 modules (5.13M mapping segments), each minified with metro-minify-terser from its code and map as the worker does. Times are single-threaded totals across all modules - medians of 15 rounds interleaving main and both diffs in this stack, Node 22.13.1 on an M5 Pro, with 95% bootstrap confidence intervals. Output was checked byte-identical in every run.

@robhogan
robhogan added this pull request to stack #1986 September 26, 2026 10:22
@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
When minifying, the transform worker converted Babel's decoded map to tuples and encoded it for the minifier, then decoded the minifier's output map with `source-map` (`toBabelSegments`), converted it back to tuples and encoded it again as the stored VLQ map. Terser already builds that decoded map, and only encodes it when `result.map` is read.

This diff:
 - Adds an optional `decodedMap` to `MinifierResult`. When a minifier provides it, the worker encodes the stored map straight from it with `vlqMapFromBabelDecodedMap`, as it already does for unminified modules. Otherwise it decodes `map` with `@jridgewell/sourcemap-codec`, which is already installed via `@babel/generator`.
 - Has `metro-minify-terser` return Terser's `decoded_map` as `decodedMap`, and encode `map` only if it's read.
 - Passes Babel's own encoded `result.map` to the minifier, rather than re-encoding it from tuples.
 - Deprecates `MinifierResult.map` in favour of `decodedMap`, and `toBabelSegments`, `toSegmentTuple` and `tuplesFromBabelDecodedMap` in `metro-source-map`, which Metro no longer uses. The next diff removes them.

Output is unchanged - across every module of our benchmark app*, the minified code, line count and stored map are byte-identical. Custom minifiers that only return `map` keep working as before.

Minifying the whole app is 10.4% faster (95% CI 7.4-11.4%; 21.3s -> 19.2s of CPU on our benchmark app*), from no longer converting the map to and from tuples, and from Terser no longer encoding its map at all:

| | Minify step (median) | vs `main` |
|---|---|---|
| `main` | 21,286ms (20,844 to 21,561) | |
| This diff | 19,228ms (18,886 to 19,773) | -10.4% (-11.4 to -7.4) |

## Changelog

```
 - **[Performance]**: Faster source map handling when minifying, using the minifier's decoded map if it provides one (`MinifierResult.decodedMap`)
 - **[Deprecated]**: Custom minifiers should return their source map as `decodedMap` - `MinifierResult.map` is deprecated
 - **[Deprecated]**: `metro-source-map`: Deprecate `toBabelSegments`, `toSegmentTuple` and `tuplesFromBabelDecodedMap`
```

## Test plan

New tests cover the worker using `decodedMap` without reading `map`, and `metro-minify-terser` returning Terser's decoded map and encoding `map` only on request.

```
yarn jest
yarn flow check
yarn lint
yarn verify-api-snapshots
```

\* Benchmark: Mattermost Mobile 2.45.0 (React Native 0.83.9), its release bundle split back into its 7,082 modules (5.13M mapping segments), each minified with `metro-minify-terser` from its code and map as the worker does. Times are single-threaded totals across all modules - medians of 15 rounds interleaving `main` and both diffs in this stack, Node 22.13.1 on an M5 Pro, with 95% bootstrap confidence intervals. Output was checked byte-identical in every run.

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