Skip to content

unplugin: support SolidStart / vinxi filesystem routers#2148

Merged
STRd6 merged 3 commits into
mainfrom
solid-start-1319
Jun 14, 2026
Merged

unplugin: support SolidStart / vinxi filesystem routers#2148
STRd6 merged 3 commits into
mainfrom
solid-start-1319

Conversation

@STRd6

@STRd6 STRd6 commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1319 (same report as solid-start#1549).

Cause: two independent breaks.

  1. vinxi's filesystem routers (analyzeModule) enumerate a route module's exports by reading the file raw off disk and parsing it with esbuild's tsx loader. Civet source fails to parse, addRoute swallows the error, and the route is silently dropped — before any vite plugin runs.
  2. The plugin's .civet.civet.tsx id rewrite breaks vinxi's manifest correspondence: route chunks are looked up in vite's build manifest by the original .civet?pick=... path, crashing prod SSR with Cannot read properties of undefined (reading 'file').

Approach:

  • Wrap each vinxi fs-router's toRoute to compile .civet routes to TSX and substitute fs.readFileSync (exact-path match, restored in finally) for the duration of the analysis. Hooked from the vite config hook (enforce: 'pre' runs before vinxi's hook that first materializes routes) and from a construction-time microtask via globalThis.app, because vinxi build builds each router in a subprocess while the parent also materializes routes without running any vite hook.
  • Skip the output-extension append when a vinxi router is detected (same shape as the existing esbuild carve-out). JSX still compiles because SolidStart forwards extensions to vite-plugin-solid.
  • Warn on ts: "preserve" under vinxi: with .civet ids, neither vite core nor vite-plugin-solid strips TypeScript syntax, so output must be type-free (civet / esbuild / tsc).

Includes a SolidStart example (integration/unplugin-examples/solid-start) with a Civet page route and API route — verified with vinxi build + serving the output, and vinxi dev — plus unit tests for the router patching and id behavior, and README/docs updates.

Longer term this wants an upstream analysis hook (sketched in the linked solid-start issue discussion); this fix works without one and could feature-detect it later.

🤖 Generated with Claude Code

vinxi's filesystem routers enumerate a route module's exports by reading
the file off disk and parsing it with esbuild's tsx loader, which fails
on Civet source and silently drops the route. Wrap each router's toRoute
to hand the analyzer compiled TSX instead, hooked from both the vite
config hook and a construction-time microtask via globalThis.app (vinxi
build materializes routes in the parent process without running any vite
hook). Also stop appending the output extension to resolved ids under
vinxi, since route chunks are looked up in vite's build manifest by the
original .civet path, and warn on ts: "preserve", which nothing
downstream can strip there.

Adds a SolidStart example with page and API routes in Civet.

Fixes #1319

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (2d68a3b) to head (f3033ab).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main     #2148    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           57        57            
  Lines        28023     28134   +111     
  Branches      5255      5281    +26     
==========================================
+ Hits         28023     28134   +111     
Files with missing lines Coverage Δ
source/unplugin/unplugin.civet 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds SolidStart/vinxi filesystem-router compatibility to the Civet unplugin. Two independent issues are fixed: vinxi's analyzeModule reads routes synchronously off disk as TSX (rejecting raw Civet source), and the .civet.civet.tsx id rewrite broke vinxi's build-manifest lookup at prod SSR time.

  • Route analysis shim: patchVinxiFsRouter wraps each router's toRoute to pre-compile .civet routes to TSX and temporarily substitute fs.readFileSync via a single module-level intercept backed by a Map<string, string>, making concurrent analyses safe. The patch runs from the vite config hook (for dev/build subprocesses) and from a construction-time queueMicrotask (for vinxi build's parent-process manifest pass where no vite hook fires).
  • Id rewrite bypass: resolveId skips appending the output extension when a vinxi router is detected (vinxi = true), matching how the esbuild framework already skips it; SolidStart's extensions forwarding to vite-plugin-solid handles JSX compilation via the id suffix instead.
  • A minimal SolidStart example app with a page route and API route, comprehensive unit tests for the patching/concurrency/restore semantics, and README/docs updates are included.

Confidence Score: 5/5

Safe to merge. The two root-cause fixes are well-scoped, the concurrency-sensitive fs.readFileSync intercept uses a correct Map+refcount pattern, and the change is covered by ten new unit tests.

Both bugs are addressed with targeted, independently testable changes. The module-level readFileSync intercept correctly handles concurrent toRoute calls. The vinxi flag is set only from the vite config hook, which is intentional. No pre-existing behavior is altered for non-vinxi users.

No files require special attention.

Important Files Changed

Filename Overview
source/unplugin/unplugin.civet Core of the fix: adds module-level concurrent-safe fs.readFileSync intercept, patchVinxiFsRouter wrapper, queueMicrotask global-app probe, vinxi flag, and output-extension bypass in resolveId/handleHotUpdate. Logic is correct; previous concurrency issue was already resolved via Map+counter approach.
test/infra/unplugin.civet New describe block adds 10 targeted tests covering router patching, compiled-TSX substitution, concurrent analyses, intercept restoration, globalThis.app microtask path, non-civet passthrough, idempotent patching, and the preserve-mode warning.
integration/unplugin-examples/solid-start/app.config.ts Minimal SolidStart config: ts:"civet" (correct — ts:"preserve" is now explicitly unsupported with vinxi), extensions:["civet"] for filesystem router scan, and the Civet vite plugin.
integration/unplugin-examples/solid-start/src/routes/index.civet Example page route — valid Civet with a JSX default export. Demonstrates the filesystem router integration working end-to-end.
integration/unplugin-examples/solid-start/src/routes/api/answer.civet Example API route — exports a GET handler using SolidStart's json helper. Validates the named-export path through vinxi's analyzeModule shim.

Reviews (2): Last reviewed commit: "unplugin: review feedback — refcounted i..." | Re-trigger Greptile

Comment thread source/unplugin/unplugin.civet Outdated
Comment thread source/unplugin/unplugin.civet
Concurrent toRoute calls (two routers analyzing in dev) could race the
save/restore of the function pointer, leaving a stale patch installed.
A single permanent intercept consults a substitution map instead, with
synchronous set/delete around the analyzer's synchronous read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@edemaine edemaine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on! Are you working on an upstream patch too?

I thought Tanstack Start used Vinxi, but apparently they migrated away last year. Nonetheless, we should probably test Tanstack Start with the unplugin at some point.

Comment thread source/unplugin/unplugin.civet Outdated
Comment thread source/unplugin/unplugin.civet Outdated
Comment thread source/unplugin/unplugin.civet Outdated
Comment thread source/unplugin/unplugin.civet Outdated
@STRd6

STRd6 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

Are you working on an upstream patch too?

Yes: solidjs/solid-start#2156

- Install the readFileSync intercept only while route analyses are in
  flight, restoring the original when the count returns to zero
- Share one compile-options load between buildStart and vinxi route
  analysis via `compileOptions ?=`, with the threads merge inside the
  loader
- Single fs default import (+ type-only Stats) instead of namespace +
  default pair

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edemaine

Copy link
Copy Markdown
Collaborator

@greptileai revise your review

@STRd6
STRd6 merged commit e7429ac into main Jun 14, 2026
17 checks passed
@STRd6
STRd6 deleted the solid-start-1319 branch June 14, 2026 16:26
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.

Solid Start file router

2 participants