Solid 2: port to solid-js 2 + @solidjs/vite-plugin start mode (SSG, router 2, meta 1.0) - #174
devagrawal09 wants to merge 12 commits into
Conversation
- solid-js / @solidjs/web 2.0.0-rc.13 - @solidjs/router 2.0.0-next.35 (+ filesystem-routing 0.4.0) - @solidjs/meta 1.0.0-next.2 - @solidjs/vite-plugin 3.0.0-next.47 replaces @solidjs/start + nitro - @kobalte/core 2.0.0-alpha.2 - @solid-primitives/* v2-targeting next prereleases
Mechanical: - solid-js/web -> @solidjs/web; JSX/ComponentProps types from the renderer package - jsxImportSource -> @solidjs/web (tsconfig + MDX compiler + twoslash) - mergeProps/splitProps -> merge/omit; Suspense -> Loading; onMount -> onSettled - Context.Provider -> context-is-provider; useCurrentMatches -> useRouteMatches - classList -> class object/array form; aria-* booleans -> enumerated strings - unplugin-icons: local ambient types (upstream types target solid-js 1.x JSX) Semantic: - createEffect split into compute/apply everywhere (10 sites) - createResource -> async createMemo in page-data (deferStream kept as no-op prop) - startTransition removed in locale switching (transitions are built in) - @solidjs/meta 1.0: MetaProvider dropped; useHead script injection -> <Script> components (ThemeCookieScript / PreferredLanguageCookieScript) rendered by Root - Solid Router 2: <A> -> native <a> + useLinkState; no useCurrentMatches - globals.ts: module-level detached root fixes per-Layout computation leak (kobaltedev#152) - startConfig() now targets @solidjs/vite-plugin start mode instead of SolidStart
- vitest projects: node (server build) + jsdom with resolve.conditions=[browser] for client-rendering tests; replaces 1.x hard-coded dist/*.cjs imports - tests/helpers/solid.ts mount(): evaluate Solid 2 lazy JSX when calling components/providers directly (children getters no longer run eagerly) - flush() where tests read synchronously after a write (microtask batching) - theme.test: Script component assertion replaces useHead mock; two-arg createEffect mock; reactive write moved out of the owned scope - page-data.test: useRouteMatches mock + async createMemo shim - preview.test: esbuild strips TS, @solidjs/babel-plugin compiles JSX (fixes the pre-existing missing @babel/preset-typescript failure on main) src/client/theme.ts: seed the theme signal from the cookie instead of writing it during getRawTheme() — Solid 2 forbids reactive writes inside computations.
Library: - src/config/file-routes.ts: solidBaseFileRoutes() on filesystem-routing (the SolidStart convention, router-neutral). md/mdx are pages out of the box; SolidBase only widens the pick to keep 57480SolidBase_page_data through route code-splitting. Wired into plugin() (opt out with fileRoutes: false). - startConfig(): extensions need their leading dot for @solidjs/vite-plugin (.md/.mdx) - page-data: resolve the page module via the lazy route component (preload/moduleUrl); the 1.x route.key. shape is kept as a fallback - vite plugin: resolve.dedupe for solid-js/@solidjs/web/router/meta — Solid 2 context identity breaks when pnpm installs two peer-variants of @solidjs/router - default-theme context: createLayeredContext instead of reading our own (absent) context — Solid 2 throws on a missing provider - client: HttpStatusCode component (httpStatus from @solidjs/web) for MDX pages Apps (dev/, docs/): - vite.config: solid(solidBase.startConfig()) replaces solidStart() + nitro() - src/Document.tsx (document shell w/ getHtmlProps + HydrationScript), src/app.tsx with createRouter + fileRoutes(pageRoutes) from virtual:file-routes; entry-*.tsx and app.config.ts removed (generated by the plugin) - docs: @solid-mediakit/og plugin + og-image route disabled (no Solid 2 release) - dev: ec-file.mdx examples point at Document.tsx/app.tsx; theme ComponentProps from web Verified: vite dev serves all routes in both apps with no SSR errors (404 status works), vite build of dev/ produces client + server bundles, vitest 128/128, tsc clean.
…; docs for the new setup Prerender (src/config/vite-plugin/prerender.ts): - buildApp (post) hook: finishes the environment builds (client first, then the rest — @solidjs/vite-plugin defers to a plugin that declares buildApp), then renders every markdown route from routes-index plus /, configured extras and crawled same-origin links through the production handleRequest() with renderMode: 'async' (head tags inlined, httpStatus honored) and writes dist/client/**/index.html + 404.html. - Enabled by default (prerender: false to deploy the server bundle); options: routes, crawlLinks, notFound, origin. Skipped (non-200) paths are logged — which already surfaced five broken links in the docs content. Generated assets (sitemap/robots/llms): emitted into the client bundle from generateBundle instead of Nitro publicAssets; dev middleware unchanged. Docs: guides and reference snippets rewritten for vite.config.ts + @solidjs/vite-plugin start mode (Document.tsx, createRouter + fileRoutes, startConfig()); 'for SolidStart' copy updated; netlify.toml publishes docs/dist/client as a static site. Verified: dev/ -> 16 pages, docs/ -> 41 pages + 404.html + sitemap (41) + robots + llms, titles/meta inlined, vitest 128/128, tsc clean.
✅ Deploy Preview for solidbase ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Importing dist/server/server.js into Vite's process left the event loop alive after prerendering (the production server runtime holds handles), so vite v7.3.6 building client environment for production... ✓ 0 modules transformed. never returned. Attribution: building the environments from the buildApp hook alone exits fine; the in-process import + render hangs. The buildApp hook now spawns prerender-worker.mjs (node, JSON over stdio), which imports the server bundle, renders the seed/crawl set with renderMode: 'async', writes dist/client/**/index.html + 404.html and exits. Same output; both apps' builds return in ~5s.
|
Pushed a fix for a build hang found while verifying: The prerender now runs in a short-lived child process ( |
publishConfig.exports resolve to dist/, where tsc does not emit the .mjs worker;
scripts/build.js now copies **/*.{js,mjs,css} from src so the published package can
spawn it (verified with pnpm pack --dry-run).
- Pages are requested at base + path (crawled links un-prefixed before queueing); output stays relative to the client out dir. Verified with base: '/sub/'. - solidBaseToRoute is exported so apps that set fileRoutes: false and wire fileRoutes() themselves keep 59015SolidBase_page_data through code-splitting.
Verification pass — findingsRan everything beyond the SSR/curl checks: frozen-lockfile install, full Fixed in this push
|
| Case | SSR hydration keys | Hydrates | Click works |
|---|---|---|---|
native <button> with <span> + <svg> |
consistent | ✅ | ✅ |
dynamic(() => "button") |
button@140 span@141 svg@143 |
✅ | ✅ |
Kobalte Dialog.Trigger |
server button@…060 span@…064 svg@…066, client expects span@…061 svg@…063 |
❌ | ❌ |
i.e. Kobalte's trigger consumes 3 extra hydration ids on the server before its children. This is kobaltedev/kobalte#717 (open; not fixed in alpha.2; the thread's <Dynamic> theory is ruled out above — dynamic() alone is fine, and a local patch of Polymorphic to dynamic() changed nothing).
Impact: the port is complete and correct for SSR/SSG, but the interactive client is non-functional until this is resolved. Default-theme Kobalte usage: Dialog ×3, Popover ×2, Select ×2, Collapsible, Badge, Tabs across 8 files.
Options:
- Wait for a Kobalte alpha with #717 fixed (upstream owner has said Polymorphic internals will be refactored for Solid 2).
- Find the 3 server-only id consumers in Kobalte's Button/Trigger and ship a
pnpm patch(workspace-only; consumers would need the same until upstream lands). - Replace the Kobalte triggers (Dialog/Popover/Select) in the default theme with native elements + small primitives — removes the alpha dependency entirely; Collapsible/Badge/Tabs could follow.
I'd lean 3 for Dialog/Popover (they are thin here) and 1/2 for Select, but that's a maintainer call.
Other observations (not blockers)
vite previewserves unmatched paths as 200 (preview-mode quirk; static hosts serve404.htmlwith 404, andhttpStatusis correct undervite devand in the SSG output).- No unit tests added yet for
file-routes/prerender; the Playwright smoke scripts are local for now and could become atests/e2eproject if wanted.
Hydration blocker resolved — root cause is upstream and already fixedThe hydration failure reported above is solidjs/solid#3741, fixed by solidjs/solid#3757 ( Mechanism: for a component prop the compiler emits Verified with
How to consume the fix (maintainer call):
Remaining dev-only diagnostics seen in |
…ttled The port replaced `startTransition(() => navigate()).then(setLang)` with `Promise.resolve(navigate())` - but Router 2's navigate() returns void, so the callback ran on the next microtask, before the navigation committed. setLocale is now a generator action: the navigation runs as a transition and onSettled() queues the lang write behind its effects, i.e. after the new route is in the DOM (verified in Chromium: h1 changes, then lang; back/forward and locale switching work with no errors). Tests call setLocale from the test body - Solid 2 rejects actions invoked inside an owned scope ([ACTION_CALLED_IN_OWNED_SCOPE]) - and flush() the effect queue.
|
Decision: wait for the next
|
…outes Review findings (independent Codex pass on the PR): - prerender-worker received Vite base but a half-applied patch never used it: requests now go to base + path and crawled links are normalized back to app paths. Verified discriminatingly with the router base set to /sub/: fixed worker prerenders all 15 dev pages, the previous one skipped all 15. - SolidStart answered unmatched paths with 404 implicitly; Router 2 start mode renders an empty shell with 200. SolidBaseRoot now sets httpStatus(404) on the server when no route matched (an app's own [...404] route still wins). - dev/docs routers pass base: import.meta.env.BASE_URL. - biome: unused helper warning gone; EOF newline in structure.mdx. Known, pre-existing: SolidBase's link helpers emit root-relative hrefs and are not base-aware.
Independent review (Codex) — findings and fixesHad a Codex agent verify the branch from scratch (install, typecheck, biome, tests, library build/pack, both app builds incl. exit behaviour, dev-server probes, diff review,
Also: biome warning (unused helper) resolved; EOF newline in Known, pre-existing, not addressed here: SolidBase's link helpers ( |
…status The Milestone 1 port turned the page-data createResource into an async createMemo and dropped deferStream as a no-op. It was not: in streaming render mode the shell (and the response head) is committed as soon as SolidBaseRoot's <Loading> boundary suspends on page data, so a [...404] page's httpStatus(404) ran after commit and was ignored. Only the async render path (prerender, crawler UAs) got 404. The server createMemo honors deferStream at runtime (dynamic() relies on it) though it is not on the public MemoOptions type yet; pass it through a typed cast and keep the 1.x default of true. Verified: docs /nope and /fr/nope -> 404 under streaming, dev unmatched -> 404, 200s unchanged, prerender unchanged (41 pages + 404.html), tests 128/128.
|
Follow-up from the Codex re-check: Fixed by restoring |
…eats the exact base path as the app root Found by an independent review (Codex GPT-6-Astra) with a scratch server fixture: - relative hrefs were resolved against the origin, so <a href="next"> on /guide/first was crawled as /next instead of /guide/next; - a link to exactly the Vite base (no trailing slash) was not stripped and was requested as /base/base. extractLinks now resolves against the page URL (base + path) and toAppPath maps base and base/ to /. Verified with the fixture under base /review-base/ and /, the dev build under base /sub/ (15 pages, 0 skipped), and the docs build, whose skipped-link count dropped from 7 to 6 because one relative link now resolves correctly. Biome is diagnostic-free on src/.
|
Second independent review, this time on Codex GPT-6-Astra (the model originally requested; it is in the Codex catalogue even though the subagent driver's advertised list omits it). It reproduced the full PASS set at e3ae444 and found two real crawler bugs via a scratch server fixture, now fixed in the latest commit:
Also from that review, informational: Kobalte |

Summary
Ports SolidBase to Solid 2 (
solid-js/@solidjs/web2.0.0-rc.13) and to@solidjs/vite-pluginstart mode, which replaced SolidStart. The library, both apps (dev/,docs/), the test suite, static site generation and the docs content are all ported. Breaking major for consumers (they have to move to Solid 2 anyway); public config/theme API and MDX authoring are kept as-is wherever possible.Draft because every upstream dependency is a prerelease (pinned exactly — see first commit) and a couple of design choices below deserve a look.
Commits (reviewable one by one)
solid-js/@solidjs/webrc.13, router2.0.0-next.35+filesystem-routing, meta1.0.0-next.2,@solidjs/vite-plugin 3.0.0-next.47(replaces@solidjs/start+nitro), Kobalte2.0.0-alpha.2,@solid-primitives/*nextsrc/tsccleanmount()helper,flush()for batching; 128/128Document.tsx,createRouter+fileRoutes(pageRoutes), entry files removedsolidbase:prerenderbuildApp hook,generateBundleasset emission, guides rewrittenWhat changed in the library
Mechanical:
solid-js/web→@solidjs/web;JSX/ComponentPropsfrom the renderer package;jsxImportSource(tsconfig and the MDX compiler + twoslash);merge/omit;Loading;onSettled; context-is-provider;classList→classarrays; booleanaria-*→ enumerated strings; local ambient types for~icons/*(upstream types target 1.x JSX).Semantic (please review):
createEffectsplit into compute/apply at 10 sites;createResource→ asynccreateMemoinpage-data(deferStreamkept as a no-op prop).getRawTheme()wrotesetTheme()inside an effect's compute phase (REACTIVE_WRITE_IN_OWNED_SCOPE). The signal is now seeded from the cookie.globals.tscreated acreateRootper Layout; now one module-level detached root.@solidjs/meta1.0 is provider-less:MetaProvidergone;useHeadscript injection →<Script>components rendered bySolidBaseRoot.meta.providerprop kept, deprecated.<A end activeClass>→ native<a>+useLinkState;useCurrentMatches→useRouteMatches; page module resolved via the lazy route component'spreload()/moduleUrl(1.xroute.key.$componentkept as a fallback).DefaultThemeComponentsProvider→createLayeredContext(v2 throws on reading a missing context).resolve.dedupeforsolid-js/@solidjs/web/router/metain the Vite plugin — pnpm produced two@solidjs/routerpeer variants (root vs docs) → two contexts. Any consumer can hit this.Routing:
src/config/file-routes.ts—filesystem-routing(the Start convention, router-neutral) already treats md/mdx as pages; SolidBase only widens thepickso$$SolidBase_page_datasurvives code-splitting. Wired intoplugin();fileRoutes: falseopts out.startConfig()now returns@solidjs/vite-pluginoptions (ssr,start, dotted.md/.mdxextensions).SSG:
solidbase:prerender(buildApppost-hook) finishes the environment builds, then renders/+ the markdown route index + extras + crawled links through the productionhandleRequest(req, { renderMode: "async" })(head tags inlined,httpStatushonored) intodist/client/**/index.html+404.html. On by default (prerender: falsefor SSR deploys). Non-200 paths are logged — this already surfaced five broken links in the docs content.Generated assets (sitemap/robots/llms) are emitted from
generateBundleinstead of NitropublicAssets.Consumer migration (apps)
Mostly deletion: remove
entry-client.tsx,entry-server.tsx,app.config.ts;vite.config.tsusessolid(solidBase.startConfig()); addsrc/Document.tsx(getHtmlProps()+<HydrationScript/>);app.tsxbecomescreateRouter({ routes: fileRoutes(pageRoutes) })wrappingSolidBaseRoot. See the rewritten Adding to Existing Project guide.Verification
vite devfordev/anddocs/: all routes, zero SSR errors, 404 status correct.vite build:dev/→ 16 pages;docs/→ 41 pages +404.html+sitemap.xml(41) +robots.txt+llms.txt;<title>/<meta>inlined;langper locale.preview.testfailures now pass),tsc -b0, biome clean.Known / follow-ups
data-theme="ystem"in server HTML: pre-existing ("system".replace("s","")), corrected client-side by the inline script; left as-is since it changes server semantics.@solid-mediakit/oghas no Solid 2 release — docs OG images disabled.2.0.0-alpha.2declares rc.3 peers; works against rc.13.with-solidbaseCLI template is still SolidStart-based; quickstart points to the guide until updated.tsccan't seesrc/virtual.d.ts(pre-existing)./md-extensions,/customization,/extending-themes,/guide/custom-themes,/fr/guide/getting-started.@solid-primitives/scroll@nextloses theuseWindowScrollPositiongeneric;unplugin-icons/types/solidtargets 1.x JSX;filesystem-routingdoesn't append&lang.extfor non-JS pick ids.solid-replis a Solid 1 library), solid-docs'osmiumtheme.