Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 6569f8c The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Automated review: Thanks for digging into this. The perf win is real on Hermes and I was able to reproduce it, but the change needs a few fixes before it can land. Details and evidence below. What
|
baseline (JSON.parse) |
PR (object literal) | |
|---|---|---|
locale require |
25.8 ms | 17.5 ms |
lottie require |
28.1 ms | 8.6 ms |
| total | 54.0 ms | 25.6 ms (−53%) |
| JS bytes allocated | 27.6 MB | 23.9 MB |
Hermes VM on the host (VM built from the matching tag, bytecode from RN's hermesc, hyperfine, 30 runs):
| baseline | PR | |
|---|---|---|
| locale 4.5 MB | 29.8 ms | 23.3 ms |
| locale 4.2 MB (ASCII only) | 28.5 ms | 18.7 ms |
| lottie 2 MB | 61.0 ms | 42.3 ms |
Peak memory was about 25–30% lower with the PR.
Semantics. __proto__ keys (top-level and nested), U+2028/U+2029, \u0000, quotes and backslashes, large numbers, -0, 5e-324, integer-like key ordering, nesting, 150k keys, a 1M-element array, and top-level arrays and strings all produce output identical to Node's JSON.parse, in both variants and after Terser.
Trade-offs worth documenting
-
Dev mode gets slower. When Hermes runs the unminified bundle from source (
hermes -lazy, like a dev bundle from the dev server), object literals are much slower thanJSON.parse(this is the V8 rationale for webpack's default):- locale: 75.8 → 175.0 ms
- ASCII locale: 62.8 → 144.2 ms
- lottie: 81.7 → 427.9 ms (peak RSS 48 → 194 MB)
-
hermesc memory at build time:
- 2 MB Lottie: 20 MB → 472 MB peak RSS
- 1M-element numeric array: 87 MB → 761 MB
- 10 MB Lottie: 59 MB → 1.67 GB, and 0.1 s → 5 s
Metro users already live with this, but it can matter on memory-limited CI runners.
-
Build time. Terser does more work: Rspack + Terser went from 630 → 1,700 ms for about 11 MB of JSON.
-
Bytecode size can go either way. Locale shrank from 8.4 to 6.3 MB, because a single non-ASCII character makes Hermes store the whole
JSON.parsestring as UTF-16. Lottie grew from 2.10 to 2.41 MB.
Blocking issues
-
Builds crash on Rspack 1.0–1.2. The peer range is
@rspack/core >=1, but thejsongenerator option only exists from Rspack 1.3.0. With the new default, 1.2.8 fails immediately:Error: unreachable: unknow module type: json at getRawGeneratorOptions (@rspack/core/dist/index.js:6869:9) at getRawModuleRule (...) -
The rule silently overrides users' own
.jsonrules. The templates spread...Repack.getAssetTransformRules()last, and the last matchingtypewins. For example, with{ test: /\.lottie\.json$/, type: 'asset/resource' }defined earlier inmodule.rules:- without this PR, the Lottie file is emitted as a separate asset (
0a5eb80f52ea67a9.json); - with this PR, it gets inlined as a JSON module.
Reproduced on Rspack 1.3.0, 1.6.0 and 2.2.7, and on webpack 5.105. A user rule that runs a custom loader over
.jsonand setstype: 'javascript/auto'would be forced back tojsonthe same way. - without this PR, the Lottie file is emitted as a separate asset (
-
No effect on webpack 5.90–5.98.
JSONParsewas added in webpack 5.99.0, and the peer range iswebpack >=5.90. On older versions the option is silently ignored, so the documented behaviour doesn't happen. -
The docs, JSDoc, changeset and option name are inverted (see above).
Compat matrix (with the PR rule appended last, as in the templates):
| bundler | result |
|---|---|
| rspack 1.0.0 / 1.2.8 | crash (unknow module type: json) |
| rspack 1.3.0 / 1.6.0 / 2.2.7 | object literal ✅, but user .json rule overridden ❌ |
| webpack 5.90.0 / 5.98.0 | ignored (still JSON.parse) |
| webpack 5.99.0 / 5.105.4 | object literal ✅, but user .json rule overridden ❌ |
Suggestion
Rather than a module rule in getAssetTransformRules, set the global generator default in Re.Pack's base config (getRepackConfig, or the bundle command's config), gated on bundler support:
module: { generator: { json: { JSONParse: false } } } // only for rspack >= 1.3 / webpack >= 5.99I tested this across the same matrix:
- output is identical (object literals);
- user
typerules are left alone (the Lottie file stays an emitted asset); - users can still opt out through their own
module.generator.json, since user config merges on top of the Re.Pack defaults; - it also covers configs that don't call
getAssetTransformRules.
Consider enabling it for production/bundle builds only, so dev-server bundles keep the faster source-parse path. If you keep a user-facing option, a name that mirrors the bundler option (e.g. jsonParse) would avoid the inversion.
Minor: the updated tests rely on hard-coded indices (rules[1], rules[2]), which will break on any reordering. expect(rules[0]?.test.source).not.toContain('json') is a weak assertion. None of the tests exercise an actual build, so none of the issues above are caught.
Checks run
On the PR branch: tsc --noEmit, biome check and all 337 @callstack/repack jest tests pass. The Release iOS build and simulator runs use the Re.Pack CLI end to end.
|
Hey, thanks for this! I spent some time testing it, because I wanted to see what we actually get on Hermes. My setup: RN 0.88, Rspack 1.7.12, iOS simulator plus Android emulator, Release builds where I only swapped the JS bundle, 15 cold starts each. For fixtures I used 3 locale files (~1.5 MB each, EN/FR/JA) and a 2 MB Lottie-like file. In release the gain is real:
I still have some concerns about turning it on by default though. OTA size. The raw bytecode gets smaller, but OTA updates ship compressed, and gzipped it goes the other way. For apps shipping translations over the air this is actually a regression.
Development. Dev bundles run as plain JS source, and there object literals are much slower for Hermes. You pay that on every reload, and the dev bundle size is the same either way, so in dev we only get the slowdown.
Build. hermesc peak memory went 273 → 622 MB (the 2 MB Lottie alone needs ~390 MB), and bundling takes ~50% longer. Could be a problem on smaller CI machines. Not every Rspack/webpack version supports it. Our peer ranges are It overrides user rules. Since the templates spread Also the docs/changeset say the opposite of what it does. It removes So my suggestion: keep the current behaviour as the default, and let users opt in if it makes sense for their app. It already works today through config, no changes needed in Re.Pack: module: { generator: { json: { JSONParse: false } } }If we want to make it easier, a What do you think? |
Summary
issue: #1461
AI Usage:
Apexhelps to update docs + tests and added a new.changesetTest plan