Skip to content

Solid 2.0 support across the toolchain, 0.2.0 - #5

Merged
pathscale merged 9 commits into
masterfrom
next/solid-2
Aug 15, 2026
Merged

Solid 2.0 support across the toolchain, 0.2.0#5
pathscale merged 9 commits into
masterfrom
next/solid-2

Conversation

@pathscale

Copy link
Copy Markdown
Owner

Adds Solid 2.0 support to all three packages without changing what a Solid 1.9 build does. 1.9 stays the default on every entry point, pluginSolidLayoutsLibrary and its application twin are untouched, and the compiler emits byte-identical output when solid is unset.

The shape

Of the four things Solid 2.0 changed that this runtime used, three are detectable at runtime from the module object and needed no fork:

what how
splitProps became omit resolved once at load: "omit" in solid
Context.Provider became the context context.Provider ?? context
useContext throws instead of returning undefined the internal defaults context carries {}, which both majors read the same way
Dynamic and createComponent moved module the only real fork

The fourth cannot be, because a module specifier resolves before any code runs. 1.9 serves them from solid-js/web; 2.0 moved them to @solidjs/web and dropped the solid-js/web subpath entirely. That is now src/renderer.ts, twenty lines, with a twin. scripts/build.mjs emits the tree twice with the twin swapped in, and dist/component.js and dist/solid-2/component.js are identical files.

Selection is an explicit specifier rather than a bundler condition:

solid-layouts/application-boundary  ->  solid-layouts/solid-2/application-boundary

An export condition was built first and removed. Both work; the specifier wins because it is a line you can read. grep answers which runtime a build is on, and a mismatch reads as a wrong-looking import rather than as a resolve failure two layers down. The condition also needed customConditions in every consumer's tsconfig to keep tsc agreeing with the bundler.

The plugins get two more names rather than an option, because the choice has to agree with pluginSolid2() and with the installed solid-js, and a name that must match its neighbour is easier to get right than a flag that must:

pluginSolid2LayoutsLibrary(),
pluginBabel({ include: /\.(?:jsx|tsx)$/ }),
pluginSolid2(),

Three things the migration guide did not say

Read out of solid-js@2.0.0-rc.0 and @solidjs/signals@2.0.0-rc.0 rather than out of the guide, and all three changed the plan:

  1. Dynamic survived unchanged. The plan predicted a rewrite to a dynamic(source) factory. dynamic() exists, but so does Dynamic, and @solidjs/web re-exports createComponent from solid-js, so the renderer needs the same two names in both majors.
  2. JSX is gone from solid-js. Core no longer declares the namespace; it belongs to the renderer now. This is why the renderer module carries the type as well as the two values.
  3. useContext throws in the implementation. getContext throws ContextNotFoundError when the resolved value is undefined, and a provider supplying undefined counts as having provided, so an empty provider shadows a real one above it with a throw. defineComponent now skips the wrapper when the setup returned no context: a correctness fix under 2.0 and a no-op under 1.9.

Verified

145 runtime tests under 1.9, unchanged. Runtime typecheck clean. Both builds emitted. 68 Rust tests, 22 compiler JS tests. The boundary specifier checked end to end through the rebuilt native binding in both bundle mode and source mode, the latter being the one @pathscale/ui uses.

Not verified

The 2.0 build has never run against an installed solid-js@2. It typechecks with skipLibCheck, so its declarations bind to @solidjs/web but nothing has checked @solidjs/web against the Solid 2 it expects. The first real test is a @pathscale/ui build on Solid 2.

Separately and not fixed here: @rsbuild/plugin-solid@1.2.2 depends on babel-preset-solid: ^1.9.12, a hard pin on Solid 1's JSX transform, and 11 @solid-primitives/* peer-pin solid-js: ^1.6.12 and call createEffect/onCleanup internally. Neither is fixable from this repository.

SOLID-2-PLAN.md carries the full record.

meh added 7 commits August 14, 2026 13:13
The message was "must declare its public Layout exports", which reads as a
missing required field. It sends you to write the list, when the question worth
asking is whether you wanted source mode at all: convention-based discovery
needs no such list, and the documentation says so in as many words.

It also said nothing about the cost of keeping one. Nothing compares the list to
the barrel, so a rename that misses it leaves the manifest naming components
that no longer exist while rejecting the ones that do, with no diagnostic
anywhere. That is worth knowing before you choose the mode, not after.

The message now names the trade and points at both ways out.
Recorded on the branch rather than in a private note, because the number
that matters contradicts the assumption: this was scoped as "three packages
need Solid 2 support" and the whole dependence is 32 sites in one 437-line
file, plus two lines of Rust. The rsbuild plugin has none at all.

The one genuinely hard piece is the four-bucket splitProps that routes every
prop through the runtime; omit() returns only a remainder, so the bucket
boundaries have to be re-derived rather than falling out of the call.
The runtime used four things Solid 2.0 changed, and three of them can be
told apart at runtime from the module object itself: `splitProps` became
`omit` and lost its subset half, `Context.Provider` became the context,
and `useContext` now throws where it used to return `undefined`. Those
are handled in place - detected once at load, or spelled so both majors
read it the same way, which is why `createContext<UIConfig>({})` now
carries a default it never needed before.

The fourth cannot. 1.9 serves `Dynamic` and `createComponent` from
`solid-js/web`; 2.0 moved them to `@solidjs/web` and dropped the old
subpath, so no single import statement resolves under both. That one
module is now `renderer.ts`, and the build emits the tree twice with its
2.0 twin swapped in. `dist/component.js` and `dist/solid-2/component.js`
are identical files; the renderer is the whole difference.

The prop routing is the part that changed shape rather than name. One
`splitProps` call returned all four buckets and made them disjoint by
construction. `omit` returns only the remainder, so the three routed
buckets are picked by name and the bucket each declared key belongs to is
decided once per component instead of falling out of the call.

145 tests pass under 1.9, unchanged. The 2.0 output is emitted and its
declarations bind to `@solidjs/web`, but `skipLibCheck` means it has not
been checked against an installed solid-js 2 yet.
The runtime ships twice because Solid 2.0 moved `Dynamic` and
`createComponent` out of `solid-js/web` and dropped that subpath. A
generated component has to import its boundary from the matching entry,
and the compiler is what writes that import, so it needs to be told which
major the build targets: `solid: 2` emits
`solid-layouts/solid-2/application-boundary` in place of
`solid-layouts/application-boundary`.

Told rather than sniffed. The compiler sees one source file, and which
Solid an application runs on is not written in it.

Default is 1 and the emission is byte-identical to before, which the test
asserts directly rather than by inspection: a host that has never heard
of this option keeps getting what it always got.

The builtins list gains 2.0's renames - Suspense to Loading, SuspenseList
to Reveal, ErrorBoundary to Errored, plus Repeat - and the source check
admits `@solidjs/web`. Neither is gated behind the option, because the
check tests a name against a module and no 1.9 build can import `Loading`
from a Solid module that does not export one.

60 Rust tests and 21 JS tests pass. Verified through the rebuilt binding,
not only through the Rust: all three of unset, 1 and 2 emit what they
should.
`pluginSolid2LayoutsLibrary` and `pluginSolid2LayoutsApplication`, beside
the existing pair rather than in place of it. They thread `solid: 2`
through to the compiler, so the library emits its boundary import against
`solid-layouts/solid-2/application-boundary` and the application aliases
that specifier to the runtime's `./solid-2` entry instead of its root.

Separate names rather than an option on the old ones, because the choice
is not independent of the rest of the build: it has to agree with
`pluginSolid2()` and with the installed `solid-js`. A name that must match
its neighbour in the plugin list is easier to get right, and to read back
later, than a flag that must:

    pluginSolid2LayoutsLibrary(),
    pluginBabel({ include: /\.(?:jsx|tsx)$/ }),
    pluginSolid2(),

Asking a runtime that predates this for `./solid-2` now says so by name
rather than quietly falling back to `main` and serving the wrong major.

Both modes verified against the real compiler, not only the plumbing:
bundle mode through a new test in library.test.js, and source mode - the
one @pathscale/ui uses - by generating a fixture both ways and reading the
header. Unset and 2 differ in exactly that one specifier and nowhere else.
Minor rather than major, and not a prerelease: 1.9 is still the default
on every entry point, `pluginSolidLayoutsLibrary` and its application twin
are untouched, and the compiler emits byte-identical output when `solid`
is unset. Nothing that installs 0.1.x breaks on 0.2.0.

All three move together because the feature spans all three: the runtime
gained a second build, the compiler gained the option that picks it, and
the plugin gained the pair of names that sets the option.

`index.js` regenerated - napi bakes the package version into the binding
loader's mismatch check, so a bump that skips it publishes a loader that
rejects its own binary.
@pathscale
pathscale changed the base branch from master to fix/name-the-source-mode-manifest August 15, 2026 00:02
@pathscale

Copy link
Copy Markdown
Owner Author

Stacked on #4. Review that one first; this PR's diff is against its branch, not master.

@pathscale
pathscale changed the base branch from fix/name-the-source-mode-manifest to master August 15, 2026 00:43
@pathscale

Copy link
Copy Markdown
Owner Author

Folded #4 into this PR rather than stacking, so this is the single PR: 8 commits, the source-mode message fix plus Solid 2.0 support across all three packages, targeting master. CI runs again now that the base is master.

@pathscale pathscale closed this Aug 15, 2026
@pathscale pathscale reopened this Aug 15, 2026
@pathscale
pathscale merged commit 9036bc6 into master Aug 15, 2026
4 checks passed
@pathscale
pathscale deleted the next/solid-2 branch August 15, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant