Skip to content

feat(rspeedy): lower let/const to var in output for QuickJS parse speed#2755

Merged
upupming merged 7 commits into
mainfrom
feat/lower-const-let-to-var
Jun 3, 2026
Merged

feat(rspeedy): lower let/const to var in output for QuickJS parse speed#2755
upupming merged 7 commits into
mainfrom
feat/lower-const-let-to-var

Conversation

@upupming

@upupming upupming commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Convert every let/const in the build output to var (QuickJS parses var faster). Two halves:

  • User source — add the SWC transform-block-scoping pass to both the background and main-thread layers, prepended on top of the existing target baseline. It is deliberately kept outside getESVersionEnvInclude (which mirrors exactly what jsc.target lowered, and is locked by a guard test), since jsc.target keeps let/const.
  • Bundler-generated code — set rspack output.environment.const = false, so the runtime/wrapper code rspack emits also uses var.

This is an intentional output change (var instead of let/const), applied as a rspeedy-core default for all builds.

Verification

  • Direct SWC check with the exact env config: const a = 1; let b = 2var a = 1; var b = 2.
  • Unit tests: transform-block-scoping present in both layers' env.include; output.environment.const === false; snapshots updated. The getESVersionEnvIncludejsc.target equivalence guard still passes (block-scoping is added separately).
  • examples/react builds clean.

Test plan

  • turbo build (example builds) + turbo api-extractor
  • Vitest (rspeedy core + react plugin)
  • changeset status shows @lynx-js/rspeedy + @lynx-js/react-rsbuild-plugin minor

Performance validation (empirical)

Measured var vs const/let on the real Lynx engine (PrimJS, via benchx_cli) — same machine, var and const/let bundles run interleaved (N=30) to cancel timing drift; walltime read from the perfetto trace via trace_processor:

walltime metric const/let (min / median) var (min / median) var faster
executeLoadedScript — chunk top-level execution (the LoadScript region) 28.98 / 30.18 ms 28.56 / 29.88 ms −1.5% / −1.0%
prepareAndEvalScript — prepare + eval 7.35 / 7.76 ms 7.15 / 7.57 ms −2.8% / −2.5%

Plus isolated parse+compile on QuickJS (PrimJS's parent engine; std.evalScript(compile_only), 1.2 MB minified, identical SWC codegen, N=120, reproduced twice): var is ~3–5% faster (min).

So in walltime — what actually affects on-device load time — var is faster across parse, eval, and full chunk execution (~1–5%). Modest but consistent.

On the CodSpeed check

CodSpeed flags …_LoadScript −7%, but that is an instruction-count number, not walltime. Those *_LoadScript benchmarks come from benchmark/react/plugins/pluginScriptLoad.mjs, which injects Codspeed.startBenchmark()/stopBenchmark() around each chunk's top-level execution (gated on typeof Codspeed !== "undefined", so they only exist under CodSpeed). CodSpeed counts instructions in that region; the same region measured locally in walltime (executeLoadedScript, table above) is faster for var. In other words var runs slightly more instructions but in less time — the instruction-count proxy diverges from real time here. The reported run was also cross-environment (base/head on different CPUs and memory, with an implausible fib +28% swing), which further reduces its reliability. The flagged regressions can be acknowledged on CodSpeed.

Summary by CodeRabbit

  • New Features

    • Build output now defaults to using var instead of const/let across runtime and main/background layers to improve QuickJS parsing performance.
    • The build toolchain now applies a block-scoping transform by default; configs can still override to retain let/const.
  • Tests

    • Added and updated tests verifying lowering behavior and that user overrides for environment/config options are respected.

Add the SWC `transform-block-scoping` pass to both the background and
main-thread layers (on top of the existing target baseline, outside
`getESVersionEnvInclude` so the target-equivalence guard stays valid), and set
rspack `output.environment.const = false` so bundler-generated runtime/wrapper
code also emits `var`. Together these convert every `let`/`const` in the build
output to `var`, which QuickJS parses faster.

This intentionally changes emitted output (var instead of let/const).
@changeset-bot

changeset-bot Bot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: af0621d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@lynx-js/rspeedy Minor
@lynx-js/react-rsbuild-plugin Minor
create-rspeedy Minor
upgrade-rspeedy Minor
@lynx-js/react-alias-rsbuild-plugin Minor

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

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds SWC's transform-block-scoping to core and React plugin SWC configs and configures Rspack to emit var instead of const in bundler runtime; includes tests and a changeset documenting minor package bumps.

Changes

Const-to-var lowering implementation

Layer / File(s) Summary
Changeset documentation
.changeset/lower-const-let-to-var.md
Documents minor version bumps for two packages with description of block-scoping transform and output environment changes.
Rspack output environment configuration
packages/rspeedy/core/src/plugins/output.plugin.ts, packages/rspeedy/core/test/plugins/output.plugin.test.ts
Injects lowerToVar config snippet setting tools.rspack.output.environment.const to false in both default and override config paths, with tests validating default and opt-out behavior.
Core SWC block-scoping baseline
packages/rspeedy/core/src/plugins/swc.plugin.ts, packages/rspeedy/core/test/plugins/swc.plugin.test.ts
Adds transform-block-scoping to core SWC env.include baseline (with opt-out via tools.swc.env.exclude) and updates snapshots/tests for production, development, and plugin-modified scenarios.
React plugin main-thread SWC configuration
packages/rspeedy/plugin-react/src/loaders.ts, packages/rspeedy/plugin-react/test/swc-config.test.ts
Prepends transform-block-scoping to main-thread SWC env.include and updates tests to separate user-provided transforms for background vs main-thread.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • luhc228
  • colinaaa
  • HuJean

Poem

🐰 I nibble through const and let,
I tuck their scopes into small vars,
Bundles hum light, parsers fret,
QuickJS blinks at tiny stars,
Hooray — builds hop fast and far.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding SWC transform-block-scoping to lower let/const to var in build output for QuickJS parse speed improvement.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lower-const-let-to-var

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/rspeedy/core/src/plugins/output.plugin.ts 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@pkg-pr-new

pkg-pr-new Bot commented May 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@lynx-js/rspeedy

npm i https://pkg.pr.new/@lynx-js/rspeedy@2755

create-rspeedy

npm i https://pkg.pr.new/create-rspeedy@2755

@lynx-js/react-rsbuild-plugin

npm i https://pkg.pr.new/@lynx-js/react-rsbuild-plugin@2755

@lynx-js/react-alias-rsbuild-plugin

npm i https://pkg.pr.new/@lynx-js/react-alias-rsbuild-plugin@2755

upgrade-rspeedy

npm i https://pkg.pr.new/upgrade-rspeedy@2755

commit: af0621d

@codspeed-hq

codspeed-hq Bot commented May 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 3 improved benchmarks
❌ 6 regressed benchmarks
✅ 74 untouched benchmarks
⏩ 26 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
004-various-update__main-thread-setAttribute__ListItemPlatformInfoSpread 100.1 µs 117.1 µs -14.51%
003-hello-list/main-thread.js_LoadScript 2 ms 2.1 ms -7.04%
004-various-update__main-thread-setAttribute__Spread 387.4 µs 414.2 µs -6.47%
006-static-raw-text__main-thread-renderMainThread 5 ms 5.3 ms -5.7%
003-hello-list__main-thread-renderMainThread 19.6 ms 20.7 ms -5.47%
003-hello-list/background.js_LoadScript 1.9 ms 2 ms -5.16%
009-eval-bench/background.js_LoadScript 888.8 µs 626.4 µs +41.89%
more faster fib(20) 18.9 µs 14.8 µs +27.96%
transform 1000 view elements 47.2 ms 40.3 ms +17.01%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/lower-const-let-to-var (af0621d) with main (b9d0624)2

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (a3e4607) during the generation of this report, so b9d0624 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

UI Judge

GEQI weighted score: 62 / 100 across 8 examples.
Average visual-correctness score: 3.3 / 5.

Dimension Weight Average Results Status
Usability & Interaction 30% 2.9 / 5 8 OK
Visual & Aesthetics 25% 3.3 / 5 8 OK
Consistency & Standards 15% 3.1 / 5 8 OK
Architecture & UX Writing 15% 3.1 / 5 8 OK
Accessibility & Performance 15% 3.3 / 5 8 OK
# Example Visual Correctness Usability & Interaction (30%) Visual & Aesthetics (25%) Consistency & Standards (15%) Architecture & UX Writing (15%) Accessibility & Performance (15%) GEQI Page Status
1 recs 2 / 5 2 / 5 3 / 5 2 / 5 2 / 5 3 / 5 48 / 100 preview OK
2 cast-grid 5 / 5 3 / 5 4 / 5 5 / 5 5 / 5 4 / 5 80 / 100 preview OK
3 citywalk-list 2 / 5 2 / 5 3 / 5 2 / 5 2 / 5 3 / 5 48 / 100 preview OK
4 fridge-search 3 / 5 3 / 5 3 / 5 4 / 5 3 / 5 4 / 5 66 / 100 preview OK
5 trip-planner 2 / 5 2 / 5 3 / 5 2 / 5 2 / 5 2 / 5 45 / 100 preview OK
6 weather-current 5 / 5 5 / 5 4 / 5 4 / 5 5 / 5 4 / 5 89 / 100 preview OK
7 product-card 5 / 5 4 / 5 4 / 5 4 / 5 4 / 5 4 / 5 80 / 100 preview OK
8 workout-plan 2 / 5 2 / 5 2 / 5 2 / 5 2 / 5 2 / 5 40 / 100 preview OK
Details

Result 1

  • Example: recs
  • Dimension: visual-correctness
  • Visual correctness: 2 / 5
  • GEQI dimensions:
    • Usability & Interaction: 2 / 5 (30%)
    • Visual & Aesthetics: 3 / 5 (25%)
    • Consistency & Standards: 2 / 5 (15%)
    • Architecture & UX Writing: 2 / 5 (15%)
    • Accessibility & Performance: 3 / 5 (15%)
  • Task: The A2UI playground preview should show date-night dining recommendations for Moonlight Terrace, Pinewood Bistro, and Sea Breeze Kitchen.

Result 2

  • Example: cast-grid
  • Dimension: visual-correctness
  • Visual correctness: 5 / 5
  • GEQI dimensions:
    • Usability & Interaction: 3 / 5 (30%)
    • Visual & Aesthetics: 4 / 5 (25%)
    • Consistency & Standards: 5 / 5 (15%)
    • Architecture & UX Writing: 5 / 5 (15%)
    • Accessibility & Performance: 4 / 5 (15%)
  • Task: The A2UI playground preview should show a cast grid for the short film Night Notes, including Lin Xia and Zhou Ning cast cards.

Result 3

  • Example: citywalk-list
  • Dimension: visual-correctness
  • Visual correctness: 2 / 5
  • GEQI dimensions:
    • Usability & Interaction: 2 / 5 (30%)
    • Visual & Aesthetics: 3 / 5 (25%)
    • Consistency & Standards: 2 / 5 (15%)
    • Architecture & UX Writing: 2 / 5 (15%)
    • Accessibility & Performance: 3 / 5 (15%)
  • Task: The A2UI playground preview should show weekend citywalk coffee picks with Rooftop Brew Room, Corner Canvas Lab, and Late Sun Roastery.

Result 4

  • Example: fridge-search
  • Dimension: visual-correctness
  • Visual correctness: 3 / 5
  • GEQI dimensions:
    • Usability & Interaction: 3 / 5 (30%)
    • Visual & Aesthetics: 3 / 5 (25%)
    • Consistency & Standards: 4 / 5 (15%)
    • Architecture & UX Writing: 3 / 5 (15%)
    • Accessibility & Performance: 4 / 5 (15%)
  • Task: The A2UI playground preview should show refrigerator search results with Siemens, Hualing, Haier, and Midea product cards.

Result 5

  • Example: trip-planner
  • Dimension: visual-correctness
  • Visual correctness: 2 / 5
  • GEQI dimensions:
    • Usability & Interaction: 2 / 5 (30%)
    • Visual & Aesthetics: 3 / 5 (25%)
    • Consistency & Standards: 2 / 5 (15%)
    • Architecture & UX Writing: 2 / 5 (15%)
    • Accessibility & Performance: 2 / 5 (15%)
  • Task: The A2UI playground preview should show a Kyoto 48-hour trip planner with Day 1 and Day 2 itinerary sections, including Monkey Park Viewpoint.

Result 6

  • Example: weather-current
  • Dimension: visual-correctness
  • Visual correctness: 5 / 5
  • GEQI dimensions:
    • Usability & Interaction: 5 / 5 (30%)
    • Visual & Aesthetics: 4 / 5 (25%)
    • Consistency & Standards: 4 / 5 (15%)
    • Architecture & UX Writing: 5 / 5 (15%)
    • Accessibility & Performance: 4 / 5 (15%)
  • Task: The A2UI playground preview should show the current weather for Austin, TX, including clear skies with light breeze.

Result 7

  • Example: product-card
  • Dimension: visual-correctness
  • Visual correctness: 5 / 5
  • GEQI dimensions:
    • Usability & Interaction: 4 / 5 (30%)
    • Visual & Aesthetics: 4 / 5 (25%)
    • Consistency & Standards: 4 / 5 (15%)
    • Architecture & UX Writing: 4 / 5 (15%)
    • Accessibility & Performance: 4 / 5 (15%)
  • Task: The A2UI playground preview should show a Wireless Headphones Pro product card with a visible Add to Cart action.

Result 8

  • Example: workout-plan
  • Dimension: visual-correctness
  • Visual correctness: 2 / 5
  • GEQI dimensions:
    • Usability & Interaction: 2 / 5 (30%)
    • Visual & Aesthetics: 2 / 5 (25%)
    • Consistency & Standards: 2 / 5 (15%)
    • Architecture & UX Writing: 2 / 5 (15%)
    • Accessibility & Performance: 2 / 5 (15%)
  • Task: The A2UI playground preview should show a weekly workout plan with five days from Monday Ramp-Up through Friday Conditioning.

Workflow run

The prior CodSpeed run compared an AMD-EPYC base against an Intel-Xeon
head; this empty commit forces a fresh run hoping for a same-CPU base so
the simulation deltas are comparable.
upupming added a commit that referenced this pull request May 29, 2026
…n CI (#2756)

## Summary

The `Vitest (Ubuntu)` job intermittently fails on `plugin-qrcode`:

```
packages/rspeedy/plugin-qrcode/test/index.test.ts:198 (and :275)
Error: Matcher did not succeed in time.
expected "renderUnicodeCompact" to be called 2 times, but got 1 times
```

Both sites do `await expect.poll(() =>
renderUnicodeCompact).toBeCalledTimes(2)`, waiting for the QR re-render
triggered when an interactive-prompt promise resolves (`resolve('bar')`
/ `resolve('main2')`). They used `expect.poll`'s **default 1000ms**
timeout, which busy CI runners routinely exceed, so the poll gives up
before the 2nd render lands. Locally (fast machine) it passes 9/9.

The dev-server tests also ran on the default **5s** test budget, even
though `waitDevCompileDone` alone allows up to 5s — so simply bumping
the poll timeout would just hit the test timeout instead.

## Failing CI (before this fix)

- `Vitest (Ubuntu)` on #2755:
https://github.com/lynx-family/lynx-stack/actions/runs/26627685311/job/78469717405
— fails with "Matcher did not succeed in time" at `index.test.ts:198`
and `:275`.

## Fix

- `vitest.config.ts`: raise the package `testTimeout` to 20s (these
tests legitimately spin up a real dev server).
- The two re-render polls: pass `{ timeout: 10_000 }`.

Test-only change (no version bump). Can't be reproduced locally (passes
here), so the validation is CI staying green.

## Verified fixed

- `Vitest (Ubuntu)` on this PR now passes:
https://github.com/lynx-family/lynx-stack/actions/runs/26629013150/job/78474014792

(The `Vitest (Windows)` failure on this PR is an unrelated CI-infra
issue — `hashFiles(...)` in the workflow template timing out on the
Windows runner — not a test failure.)

## Test plan

- [x] `Vitest (Ubuntu)` passes.
- [ ] `Vitest (Windows)` — re-run to clear the unrelated `hashFiles`
infra timeout.
@upupming upupming marked this pull request as ready for review June 3, 2026 02:53
@upupming upupming requested review from colinaaa and luhc228 as code owners June 3, 2026 02:53
@upupming upupming enabled auto-merge (squash) June 3, 2026 04:09
colinaaa
colinaaa previously approved these changes Jun 3, 2026
Comment thread packages/rspeedy/core/src/plugins/output.plugin.ts Outdated
Place the `output.environment.const: false` default first in the
mergeRsbuildConfig chain so user-supplied
`tools.rspack.output.environment.const` can override it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b35e87937d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/rspeedy/plugin-react/src/loaders.ts
@upupming upupming force-pushed the feat/lower-const-let-to-var branch from b35e879 to d4b61b4 Compare June 3, 2026 06:29
Listing `'transform-block-scoping'` in `tools.swc.env.exclude` now skips
the let/const → var lowering while leaving the rest of the baseline
intact. Pairs with the runtime/wrapper opt-out from the rspack
`output.environment.const` config.
@upupming upupming force-pushed the feat/lower-const-let-to-var branch from d4b61b4 to a3973f4 Compare June 3, 2026 06:32
Spread `swcLoaderOptions.env` so the user's `env.exclude` reaches the
main-thread SWC loader; SWC gives exclude precedence, so listing
`'transform-block-scoping'` in `tools.swc.env.exclude` now opts both
layers out of the let/const → var lowering.
@upupming upupming merged commit 9496e4c into main Jun 3, 2026
73 of 77 checks passed
@upupming upupming deleted the feat/lower-const-let-to-var branch June 3, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants