Skip to content

modules: apply Solid components and router factories from .rx (MO-11) - #69

Draft
brenelz wants to merge 1 commit into
solidjs:mainfrom
brenelz:feat/rx-router
Draft

brenelz wants to merge 1 commit into
solidjs:mainfrom
brenelz:feat/rx-router

Conversation

@brenelz

@brenelz brenelz commented Nov 14, 2026

Copy link
Copy Markdown

A .rx app could not use @solidjs/router. A JS component applied in markup was UNKNOWN_COMPONENT, a component passed as a value was COMPONENT_AS_VALUE, and a JS module's .d.rx could not declare a component at all (PARSE). This blocks a routed version of the solid-v2/with-reactivescript template (solidjs/templates#347).

Root cause

The language had no way to talk about a component that lives in JS. A .d.rx for a JS module accepted only fn, error and type, and the checker refused any component that was not applied in a view, because .rx has no way to apply a value. @solidjs/router 2.0 (2.0.0-next.44) needs both: createRouter({ routes }) returns the router component, and each route names its page as a component value. Its links are plain <a href>; the 0.x <Router>, <Route> and <A> components are gone.

What changed

Spec 09 gains MO-11 (taken by default, open to veto) with a design note that weighs three options: declared components, a routing form in the language, and a TSX route table. It takes the first, because it uses the router's API as it is and works for any Solid component library.

  • MO-11a. A JS .d.rx may declare export component X(props: { … }), with TypeScript field types, optional catches(…) per field, and fails(…) and shape(…) after. With no children it lowers to <X /> rather than children={[]}, because next.44's router reads props.children as its root layout.
  • MO-11f. A JS fn whose result is Component (or Component<P>) is a factory. Its call runs once where it is bound, and the bound name is applied as a component (<Router />). It cannot be exported, because only its own module knows what it was given.
  • MO-11c. A component is a value only on a JS component's Component prop or inside a factory's argument literals. There it is checked as if applied, with no arguments, beneath that application: escaping failures (R-f-5), required contexts (RG-7b), the client-only first load (CN-3d) and HTML nesting (SV-7d) are all checked above <Router /> for every route. DV-24 records the assumption that the library renders it beneath the application it was passed to, and the precision it costs: a provide or errored inside a layout route is not credited to its pages.
  • MO-11d/e. Hooks are MO-10 primitives (useParams returns a store whose fields track; useNavigate returns an io function), and a route's data is an await over its params.

Both the reference (ref/) and the crate (compiler/) implement it, with the TSX projection writing .d.ts stubs for declared components and factories. New scenarios: S-module-5 (declared components and component values) and S-module-6 (the router), both checked only, since the runners have no router. examples/router/ routes between two pages with a param and per-route data, and compiler/test/vite.mjs builds it and navigates it in Chromium. It fetches @solidjs/router@2.0.0-next.44 with npm pack into .out/, so it needs the network on a fresh run. The guide gains a "Components and routers" section.

How it was verified

  • npm test in ref/: 132 scenarios pass.
  • npm run crate: 386/386. npm run crate -- --lower: 215/215, with Solid's compiler built at ref/SOLID_NEXT. With a newer ../solid-next, 23 existing programs differ on both sides of this change.
  • npm run crate -- --project: every program agrees; the one FAIL prelude line is already on main.
  • npm run types, and again with RX_CRATE=1: 179/179.
  • cargo test --release, npm test in compiler/, and npm test in tooling/rx-language-server (16 + 4) pass.
  • SOLID_VITE_PLUGIN=<a 3.0.0-next.57 build> node compiler/test/vite.mjs: every app builds, including both router runs (links change pages without a reload, a param and its data reach the page, navigate() works, the page's NotFound boundary shows, back works, a deep link loads). todo (dev) fails with a refused connection, as it does on main.
  • The with-reactivescript template with a router added builds with vite build, navigates the same way in Chromium (one document load), passes rx-tsc --noEmit against the router's own .d.ts, passes its Vitest tests, and works under the dev server.
  • npm run compiled fails S-guess-reask-refresh, S-latest-reask, S-solid-44 and S-solid-46, identically on main.

Left open

These are listed as decision 8 in spec/09-modules.md.

  • A factory's component cannot take a component value itself, so next.44's root layout as <Router>{Layout}</Router> is refused; use a parent route with children instead. Markup children on <Router> are accepted and fail at run time.
  • A JS component's own failures default to none rather than Error.
  • No declaration word yet credits a layout route's errored and provide to its pages (DV-24's precision).
  • The hooks' router context is not checked statically.
  • .rx cannot pass a type argument to useParams<{ id: string }>(), so a param is string | undefined in rx-tsc.
  • Not covered: TSX importing an .rx page (overlaps Type an entry's default export; stop rx-tsc crashing on .d.rx #65), and the router under SSR.

🤖 Generated with Claude Code

A JS module's .d.rx can declare a component, and a JS fn whose result is
`Component` hands back one. A component is a value only on a JS component's
`Component` prop or inside a factory's argument literals, where it is checked
as applied beneath that application (DV-24). This is what @solidjs/router
2.0's createRouter({ routes }) needs.

This branch has not been deployed

No deployments
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