Skip to content

feat: frontends option for languages that lower to Solid, with .rx built in - #471

Merged
ryansolid merged 4 commits into
nextfrom
feat/frontends
Oct 27, 2026
Merged

ryansolid merged 4 commits into
nextfrom
feat/frontends

Conversation

@ryansolid

@ryansolid ryansolid commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

This adds a frontends option. A language that lowers to Solid can claim a file extension and compile its own modules:

solid({ frontends: { ".x": x() } })

.rx (ReactiveScript) is built in. If the project has @reactivescript/compiler installed, .rx modules compile with plain solid().

  • The plugin resolves @reactivescript/compiler/vite from the project root the first time it meets an .rx module. A project without .rx files never loads it.
  • If the package is missing, the module fails with an install hint. The load is retried on the next .rx module, so installing the package fixes a running server.
  • A .rx entry in frontends replaces the built-in one.

A front end implements compile(source, { id, moduleId, ssr, root, solid, addWatchFile }) and returns { code, map?, css? }, or null to pass the module through.

  • id is the file path. moduleId is the full id with its query, so a front end that imports its own modules under a query (e.g. ./x.rx?rx-dep) can tell the forms apart.
  • solid holds the compiler options this plugin would use for a JSX module in that environment (generate, hydratable, moduleName, dev, …), so a front end that ends in @solidjs/compiler compiles the same way.

The result then takes the lowered .tsrx route:

  • the lazy and HMR refresh passes;
  • server functions, with IDs from the module's real path;
  • css as a CSS module (?solid-tsrx-css), including on hot update;
  • dep scanning (optimizeDeps.extensions);
  • source maps combined back to the front end's map.

Solid's own extensions can't be claimed. Today extensions parses its files as JSX text, which a language with its own syntax can't use, and .tsrx is routed by name.

What changed:

  • src/index.ts: the types, validation and routing, and the lazily loaded built-in .rx.
  • src/server-functions/index.ts: front-end extensions are included, and lowered modules are handled after Solid's pass.
  • src/tsrx.ts: the CSS query alone identifies the stylesheet module.
  • A README section and a changeset.

Dependency: server functions in front-end modules need solidjs/solid#4320, which is merged and will ship in the next compiler release. That test is skipped until then (the directivesTakeAnyExtension() probe).

Testing

  • New src/frontends.test.ts uses a .fe front end. It covers:
    • client dom compile and SSR;
    • the CSS module import and load;
    • map sources;
    • the queried moduleId;
    • addWatchFile;
    • refusing .tsx;
    • server functions.
  • It also covers built-in .rx, against a stub @reactivescript/compiler in a temp project, plus the install hint when the package is missing. 10/10 pass.
  • The full unit suite passes, 46/46, with #4320's compiler linked in. tsc --noEmit is clean.
  • End to end: @reactivescript/compiler (a Node-API build of ReactiveScript's compiler, Rust crate: front half, resolved model (CP-6), lowering through compile_program, and @reactivescript/compiler ryansolid/reactivescript#6) builds all 12 example apps through plain solid() with Vite 8.
    • Each app is driven in Chromium. The todo app also runs under the dev server.
    • A page with two programs that share a module bundles that module once.
    • 14 pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq

A front end claims a file extension and compiles its modules itself:
compile(source, { id, ssr, root, solid, addWatchFile }) returns
{ code, map, css }, given the compiler options a JSX module would get in
that environment. The module then takes the lowered .tsrx route: lazy and
refresh passes, server functions keyed by the real module path, css as a
CSS module, dep scanning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
@changeset-bot

changeset-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b8cc5fe

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

This PR includes changesets to release 1 package
Name Type
@solidjs/vite-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

@pkg-pr-new

pkg-pr-new Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/vite-plugin@471

commit: b8cc5fe

A front end that imports its own modules under a query (ReactiveScript
compiles `./x.rx?rx-dep` as a dependency, a plain `x.rx` as a program
entry) needs the query; `id` stays the clean file path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
ryansolid pushed a commit to ryansolid/reactivescript that referenced this pull request Oct 27, 2026
…lain solid()

- The crate's Node-API build (`--features node`, src/node.rs) over `app`, now a
  library function (src/app.rs) that `rxc app` also calls; a missing entry is
  an error instead of an empty program.
- package.json for @reactivescript/compiler: one binary per platform
  (npm/*, as @solidjs/compiler ships), the napi loader, and the Vite front
  end at `/vite` (moved from vite-plugin-rx/, now calling the binding instead
  of spawning rxc). Solid's plugin loads it on its own
  (solidjs/solid-vite-plugin#471), so the examples build with `solid()`.
- .github/workflows/compiler.yml: build and test each platform; on main,
  publish a new version by trusted publishing and tag compiler-v<version>.
- spec/11: the Vite paragraph, and decision 7 (taken by default).

Tests: npm test (binding, every target); compiler/test/vite.mjs 14 pass on
plain solid(); crate conformance 280/280.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
`.rx` (ReactiveScript) compiles with no option when the project has
`@reactivescript/compiler`: its `/vite` front end is resolved from the project
root the first time an `.rx` module is compiled, so a project without `.rx`
files never loads it. A missing package fails the module with an install hint
(and is retried, so installing it fixes a running server). A `.rx` entry in
`frontends` replaces the built-in one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
@ryansolid ryansolid changed the title feat: frontends option for languages that lower to Solid feat: frontends option for languages that lower to Solid, with .rx built in Oct 27, 2026
@ryansolid
ryansolid merged commit 071f74b into next Oct 27, 2026
6 checks passed
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.

2 participants