Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/tall-hoops-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/file-routes": minor
"@solidjs/router": minor
---

Move file-system routing from SolidStart into router-neutral packages: `@solidjs/file-routes` scans a route directory into a neutral route manifest with pluggable conventions, `@solidjs/file-routes/vite` delivers it as the `solid:file-routes` virtual module with HMR and per-export code splitting, and `@solidjs/router/fs` ships Solid Router's emission adapter — `createFileRoutes` and `<FileRoutes>`.
6 changes: 3 additions & 3 deletions .github/workflows/dist-typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Build package and dependencies
- name: Build packages
run: pnpm build

- name: Check types
run: pnpx @arethetypeswrong/cli --pack . --profile esm-only
- name: Check package types
run: pnpm typecheck:dist
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
run: pnpm i --frozen-lockfile

- name: Run tests
run: pnpm run test
run: pnpm test
34 changes: 34 additions & 0 deletions examples/file-routes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# File routes example

A plain Vite + Solid Router SPA using file-system routing — no SolidStart.

- `@solidjs/file-routes/vite` scans `src/routes` and serves the route manifest
from the `solid:file-routes` virtual module (see [vite.config.ts](vite.config.ts))
- `<FileRoutes />` from `@solidjs/router/fs` renders it as lazy, code-split
route definitions (see [src/entry.tsx](src/entry.tsx))

```
src/routes/
├── index.tsx → /
├── about.tsx → /about
├── blog.tsx → layout for /blog/*
├── blog/
│ ├── index.tsx → /blog
│ └── [id].tsx → /blog/:id (route config with preload)
└── [...404].tsx → catch-all
```

`blog/[id].tsx` also shows the code-splitting seam: its `route` export
(`preload`) is picked into the main bundle so data loading starts during
navigation, while the component stays in its own lazy chunk.

## Run it

From the repository root:

```sh
pnpm install
pnpm build # builds @solidjs/router and @solidjs/file-routes first
cd examples/file-routes
pnpm dev
```
12 changes: 12 additions & 0 deletions examples/file-routes/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Solid Router · File Routes</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/entry.tsx"></script>
</body>
</html>
21 changes: 21 additions & 0 deletions examples/file-routes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "example-file-routes",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@solidjs/file-routes": "workspace:*",
"@solidjs/router": "workspace:*",
"solid-js": "^1.9.3"
},
"devDependencies": {
"typescript": "^5.7.2",
"vite": "^6.0.0",
"vite-plugin-solid": "^2.11.0"
}
}
25 changes: 25 additions & 0 deletions examples/file-routes/src/entry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* @refresh reload */
import { A, Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/router/fs";
import { Suspense, type ParentProps } from "solid-js";
import { render } from "solid-js/web";

function Layout(props: ParentProps) {
return (
<>
<nav>
<A href="/">Home</A> · <A href="/about">About</A> · <A href="/blog">Blog</A>
</nav>
<Suspense>{props.children}</Suspense>
</>
);
}

render(
() => (
<Router root={Layout}>
<FileRoutes />
</Router>
),
document.getElementById("app")!
);
3 changes: 3 additions & 0 deletions examples/file-routes/src/routes/[...404].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function NotFound() {
return <h1>Page not found</h1>;
}
3 changes: 3 additions & 0 deletions examples/file-routes/src/routes/about.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function About() {
return <h1>About</h1>;
}
11 changes: 11 additions & 0 deletions examples/file-routes/src/routes/blog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ParentProps } from "solid-js";

// `blog.tsx` is the layout for everything under `blog/`.
export default function BlogLayout(props: ParentProps) {
return (
<section>
<h1>Blog</h1>
{props.children}
</section>
);
}
29 changes: 29 additions & 0 deletions examples/file-routes/src/routes/blog/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
createAsync,
query,
useParams,
type RouteDefinition
} from "@solidjs/router";

const getPost = query(async (id: string) => {
// stands in for a real fetch
await new Promise(resolve => setTimeout(resolve, 100));
return { title: `Post #${id}`, body: `This is post number ${id}.` };
}, "post");

// The `route` export is picked into the main bundle and starts the fetch
// during navigation, before this component's code-split chunk loads.
export const route = {
preload: ({ params }) => getPost(params.id!)
} satisfies RouteDefinition<"/blog/:id">;

export default function Post() {
const params = useParams<{ id: string }>();
const post = createAsync(() => getPost(params.id));
return (
<article>
<h2>{post()?.title}</h2>
<p>{post()?.body}</p>
</article>
);
}
16 changes: 16 additions & 0 deletions examples/file-routes/src/routes/blog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { A } from "@solidjs/router";
import { For } from "solid-js";

export default function BlogIndex() {
return (
<ul>
<For each={[1, 2, 3]}>
{id => (
<li>
<A href={`/blog/${id}`}>Post #{id}</A>
</li>
)}
</For>
</ul>
);
}
11 changes: 11 additions & 0 deletions examples/file-routes/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function Home() {
return (
<>
<h1>Home</h1>
<p>
Routes in <code>src/routes</code> are scanned by <code>@solidjs/file-routes/vite</code> and
rendered by <code>&lt;FileRoutes /&gt;</code> from <code>@solidjs/router/fs</code>.
</p>
</>
);
}
13 changes: 13 additions & 0 deletions examples/file-routes/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true
},
"include": ["src", "vite.config.ts"]
}
14 changes: 14 additions & 0 deletions examples/file-routes/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { fileRoutes } from "@solidjs/file-routes/vite";
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";

export default defineConfig({
// `extensions` makes vite-plugin-solid also compile the `?pick=` route
// modules the file-routes plugin emits (their ids end in a query string)
plugins: [solid({ extensions: [".jsx", ".tsx"] }), fileRoutes()],
optimizeDeps: {
// pre-bundle everything the workspace-linked router pulls in, so the
// dep optimizer doesn't re-run mid-page-load with a second solid-js copy
include: ["solid-js", "solid-js/web", "solid-js/store"]
}
});
62 changes: 7 additions & 55 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,63 +1,15 @@
{
"name": "@solidjs/router",
"description": "Universal router for SolidJS",
"author": "Ryan Carniato",
"contributors": [
"Ryan Turnquist"
],
"license": "MIT",
"version": "0.16.2",
"homepage": "https://github.com/solidjs/solid-router#readme",
"repository": {
"type": "git",
"url": "https://github.com/solidjs/solid-router"
},
"publishConfig": {
"access": "public"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"solid": "./dist/index.jsx",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"sideEffects": false,
"name": "solid-router-monorepo",
"private": true,
"scripts": {
"build": "rm -rf dist && tsc && rollup -c",
"prepublishOnly": "npm run build",
"test": "vitest run && npm run test:types",
"test:watch": "vitest",
"test:types": "tsc --project tsconfig.test.json",
"pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
"build": "pnpm --recursive --if-present run build",
"test": "pnpm --recursive --if-present run test",
"pretty": "pnpm --recursive --if-present run pretty",
"typecheck:dist": "pnpm --recursive --if-present run typecheck:dist",
"release": "pnpm build && changeset publish"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-typescript": "^7.26.0",
"@changesets/cli": "^2.27.10",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-node-resolve": "15.3.0",
"@rollup/plugin-terser": "0.4.4",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.0",
"babel-preset-solid": "^1.9.3",
"jsdom": "^25.0.1",
"prettier": "^3.4.1",
"rollup": "^4.27.4",
"solid-js": "^1.9.3",
"typescript": "^5.7.2",
"vite": "^6.0.0",
"vite-plugin-solid": "^2.11.0",
"vitest": "^2.1.6"
},
"peerDependencies": {
"solid-js": "^1.8.6"
"@changesets/cli": "^2.27.10"
},
"packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8"
}
Loading
Loading