Skip to content

feat(compiler): add an optimize option for constant folding and DCE - #3231

Draft
lxsmnsyc wants to merge 4 commits into
solidjs:nextfrom
lxsmnsyc:compiler-optimize-option
Draft

lxsmnsyc wants to merge 4 commits into
solidjs:nextfrom
lxsmnsyc:compiler-optimize-option

Conversation

@lxsmnsyc

@lxsmnsyc lxsmnsyc commented Sep 3, 2026 •

Copy link
Copy Markdown
Member

Add an optimize option to @solidjs/compiler

optimize: true (default false) runs a constant-folding and dead-code-elimination pass over the parsed program before JSX is lowered. Anything it resolves never reaches a generate at all: the element pays for no component call, memo, or insert hole, and its markup joins the surrounding template.

What folds

  • Constant expressions: literals, template literals, and the unary, binary, logical, and conditional operators over them.
  • Constant bindings at any scope: const, and let that nothing ever writes to. References resolve through oxc_semantic, so a const DEBUG = false inside a component folds at its use sites while a same-named binding in another scope is a different symbol and is left alone. var is excluded, since a read before its declaration sees undefined rather than throwing.
  • Unreachable statements: if/else branches, while (false) loops, and anything after a return, throw, break, or continue.
  • A discarded condition still runs. Every fold that reads a condition also discards it. When the condition is effectful but statically decided ([effect()], { k: effect() }, a class with a static block), the branch still folds and the condition is resequenced in front of the result: [b()] ? c() : d() becomes ([b()], c()). Component props are the exception and stay unfolded, since Solid reads them inside a memo.

Control-flow components

Tag Resolves to
<Show when> its children, or its fallback
<For each> its fallback when each is [] or statically falsy
<Repeat count> its fallback when count is below one
<Switch> / <Match when> drops statically false matches, collapses to a statically true one, falls back when every match is false
<Dynamic component> the element itself, when component is a static intrinsic tag name

<Portal>, <Loading>, <Errored>, and <Reveal> deliberately never fold. Each gates on runtime state (a mount target, a pending read, a thrown error, a reveal order) that no static analysis can decide.

Rules that keep a fold from changing behavior

  • Identity before name. Being in builtIns only makes a name a candidate. A tag folds only when it resolves to Solid's own component: either nothing declares the name and the compiler auto-imports it, or it is a top-level value import from moduleName or solid-js. A local Show, or one imported from an unrelated module, is a different component and is left alone.
  • Aliases resolve by exported name. <Cond> from import { Show as Cond } from "solid-js" folds as Show. import { Show as Cond } from "./my-show" does not fold.
  • No spread attributes. A spread can supply or override the very prop the fold reads.
  • No function children. The runtime decides from their arity whether to call them.
  • Conservative constant lattice. The evaluator declines any fold whose spelling could diverge from a real engine: string and number coercions outside the safe-integer range, non-ASCII relational comparison, and string-to-number coercion. A missed fold costs bytes, a wrong fold costs correctness.
  • One accepted deviation. A reference that runs before its declaration is a temporal dead zone throw at runtime, and folding turns it into a value. This matches what production minifiers do and is called out in the README.
  • Hoisting is respected. A branch carrying a var or function declaration is never removed.

Consistency requirement

Folding changes the shape of the rendered tree, and with it hydration ids. A server build and its client build must be compiled with the same optimize value. This is called out in the README, the TypeScript types, and the changeset.

Files

Path
src/optimize/mod.rs pass driver, JSX control-flow folding, statement DCE
src/optimize/value.rs the constant lattice and its ECMAScript operator semantics
src/shared/ast_builder.rs builder for an intrinsic element, used by the <Dynamic> fold
src/compiler.rs, src/config.rs, src/node_adapter.rs, src/lib.rs option plumbing
index.js, types.d.ts, README.md JavaScript surface, boolean validation, docs

Testing

  • 17 Rust unit tests covering the folding rules, the constant lattice edge cases (NaN, -0, ToInt32 wrapping, 1 ** Infinity), scope resolution, and every built-in identity case.
  • 9 JavaScript tests covering the option surface, generated output, and SSR parity.
  • Full suite green: 5357 JavaScript tests across 37 files. Rust tests pass under the default features, --no-default-features, and --features tsrx.
  • rustfmt clean, and clippy produces no new warnings.

Scope

Compiler only. @solidjs/babel-plugin does not gain the option, so the option-matrix parity suite is untouched. The pass is off by default, so existing output is unchanged.

@changeset-bot

changeset-bot Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0ce6be7

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

This PR includes changesets to release 11 packages
Name Type
@solidjs/compiler Minor
@solidjs/babel-plugin Minor
@solidjs/diagnostics Minor
@solidjs/element Minor
@solidjs/h Minor
@solidjs/html Minor
@solidjs/signals Minor
solid-js Minor
@solidjs/universal Minor
@solidjs/web Minor
test-integration Patch

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

@codspeed

codspeed Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 136 untouched benchmarks


Comparing lxsmnsyc:compiler-optimize-option (0ce6be7) with next (3424f9a)

Open in CodSpeed
@lxsmnsyc

lxsmnsyc commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

converted to draft for now. This is barely enough, and I think the bigger win is still in component inlining.

In the mean time, it all goes here: http://github.com/lxsmnsyc/solid-optimizer/

If the plan to reduce hydration keys, compact templates, fold memos and signals, I will bring this back.

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

Labels

None yet

1 participant