@evjs/cli 0.0.1-rc.18 → 0.0.1-rc.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -8
- package/dist/create-webpack-config.js +5 -2
- package/dist/index.js +5 -91
- package/package.json +7 -10
- package/templates/basic-csr/README.md +0 -23
- package/templates/basic-csr/index.html +0 -11
- package/templates/basic-csr/package.json +0 -21
- package/templates/basic-csr/src/main.tsx +0 -22
- package/templates/basic-csr/src/pages/__root.tsx +0 -28
- package/templates/basic-csr/src/pages/about.tsx +0 -19
- package/templates/basic-csr/src/pages/home.tsx +0 -19
- package/templates/basic-csr/src/pages/posts/index.tsx +0 -63
- package/templates/basic-csr/tsconfig.json +0 -17
- package/templates/basic-server-fns/README.md +0 -24
- package/templates/basic-server-fns/index.html +0 -11
- package/templates/basic-server-fns/package.json +0 -21
- package/templates/basic-server-fns/src/api/users.server.ts +0 -31
- package/templates/basic-server-fns/src/main.tsx +0 -12
- package/templates/basic-server-fns/src/routes.tsx +0 -108
- package/templates/basic-server-fns/tsconfig.json +0 -16
- package/templates/complex-routing/README.md +0 -27
- package/templates/complex-routing/index.html +0 -11
- package/templates/complex-routing/package.json +0 -21
- package/templates/complex-routing/src/api/data.server.ts +0 -86
- package/templates/complex-routing/src/main.tsx +0 -29
- package/templates/complex-routing/src/pages/__root.tsx +0 -60
- package/templates/complex-routing/src/pages/catch.tsx +0 -26
- package/templates/complex-routing/src/pages/dashboard.tsx +0 -81
- package/templates/complex-routing/src/pages/home.tsx +0 -35
- package/templates/complex-routing/src/pages/posts/index.tsx +0 -104
- package/templates/complex-routing/src/pages/search.tsx +0 -71
- package/templates/complex-routing/src/pages/user.tsx +0 -32
- package/templates/complex-routing/tsconfig.json +0 -13
- package/templates/configured-server-fns/README.md +0 -24
- package/templates/configured-server-fns/ev.config.ts +0 -56
- package/templates/configured-server-fns/index.html +0 -11
- package/templates/configured-server-fns/package.json +0 -21
- package/templates/configured-server-fns/src/api/users.server.ts +0 -22
- package/templates/configured-server-fns/src/main.tsx +0 -12
- package/templates/configured-server-fns/src/routes.tsx +0 -111
- package/templates/configured-server-fns/tsconfig.json +0 -16
- package/templates/with-tailwind/ev.config.ts +0 -20
- package/templates/with-tailwind/index.html +0 -11
- package/templates/with-tailwind/package.json +0 -28
- package/templates/with-tailwind/postcss.config.mjs +0 -5
- package/templates/with-tailwind/src/main.tsx +0 -15
- package/templates/with-tailwind/src/pages/__root.tsx +0 -30
- package/templates/with-tailwind/src/pages/home.tsx +0 -85
- package/templates/with-tailwind/src/styles.css +0 -2
- package/templates/with-tailwind/tsconfig.json +0 -17
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createRootRoute,
|
|
3
|
-
createRoute,
|
|
4
|
-
Link,
|
|
5
|
-
Outlet,
|
|
6
|
-
} from "@evjs/runtime/client";
|
|
7
|
-
import { useEffect, useState } from "react";
|
|
8
|
-
import { createUser, getUsers } from "./api/users.server";
|
|
9
|
-
|
|
10
|
-
// ── Root Route ──
|
|
11
|
-
|
|
12
|
-
function Root() {
|
|
13
|
-
return (
|
|
14
|
-
<div style={{ fontFamily: "system-ui, sans-serif", padding: "1rem" }}>
|
|
15
|
-
<h1>Server Functions Example</h1>
|
|
16
|
-
<nav style={{ display: "flex", gap: "1rem", marginBottom: "1rem" }}>
|
|
17
|
-
<Link to="/">Users</Link>
|
|
18
|
-
</nav>
|
|
19
|
-
<Outlet />
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const rootRoute = createRootRoute({ component: Root });
|
|
25
|
-
|
|
26
|
-
// ── Users Route ──
|
|
27
|
-
|
|
28
|
-
function UsersPage() {
|
|
29
|
-
const [name, setName] = useState("");
|
|
30
|
-
const [email, setEmail] = useState("");
|
|
31
|
-
|
|
32
|
-
const [users, setUsers] = useState<
|
|
33
|
-
{ id: string; name: string; email: string }[]
|
|
34
|
-
>([]);
|
|
35
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
36
|
-
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
let mounted = true;
|
|
39
|
-
getUsers()
|
|
40
|
-
.then((data) => {
|
|
41
|
-
if (mounted) {
|
|
42
|
-
setUsers(data);
|
|
43
|
-
setIsLoading(false);
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
.catch((err) => {
|
|
47
|
-
console.error(err);
|
|
48
|
-
if (mounted) setIsLoading(false);
|
|
49
|
-
});
|
|
50
|
-
return () => {
|
|
51
|
-
mounted = false;
|
|
52
|
-
};
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
async function handleCreate(e: { preventDefault: () => void }) {
|
|
56
|
-
e.preventDefault();
|
|
57
|
-
if (!name || !email) return;
|
|
58
|
-
try {
|
|
59
|
-
await createUser({ name, email });
|
|
60
|
-
setName("");
|
|
61
|
-
setEmail("");
|
|
62
|
-
const data = await getUsers();
|
|
63
|
-
setUsers(data);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
console.error(err);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (isLoading) return <p>Loading users from server…</p>;
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div>
|
|
73
|
-
<h2>Users (fetched via direct server function call)</h2>
|
|
74
|
-
<ul>
|
|
75
|
-
{users.map((u) => (
|
|
76
|
-
<li key={u.id}>
|
|
77
|
-
{u.name} — {u.email}
|
|
78
|
-
</li>
|
|
79
|
-
))}
|
|
80
|
-
</ul>
|
|
81
|
-
|
|
82
|
-
<h3>Add User</h3>
|
|
83
|
-
<form onSubmit={handleCreate} style={{ display: "flex", gap: "0.5rem" }}>
|
|
84
|
-
<input
|
|
85
|
-
placeholder="Name"
|
|
86
|
-
value={name}
|
|
87
|
-
onChange={(e) => setName(e.target.value)}
|
|
88
|
-
/>
|
|
89
|
-
<input
|
|
90
|
-
placeholder="Email"
|
|
91
|
-
value={email}
|
|
92
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
93
|
-
/>
|
|
94
|
-
<button type="submit">Create</button>
|
|
95
|
-
</form>
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const usersRoute = createRoute({
|
|
101
|
-
getParentRoute: () => rootRoute,
|
|
102
|
-
path: "/",
|
|
103
|
-
component: UsersPage,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ── Route Tree ──
|
|
107
|
-
|
|
108
|
-
export const routeTree = rootRoute.addChildren([usersRoute]);
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"include": ["src"],
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"target": "ESNext",
|
|
5
|
-
"jsx": "react-jsx",
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
8
|
-
"moduleResolution": "Bundler",
|
|
9
|
-
"outDir": "./dist",
|
|
10
|
-
"rootDir": "./src",
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"strict": true,
|
|
13
|
-
"noEmit": true,
|
|
14
|
-
"verbatimModuleSyntax": true
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# complex-routing
|
|
2
|
-
|
|
3
|
-
Advanced routing patterns with TanStack Router.
|
|
4
|
-
|
|
5
|
-
## Run
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm run dev -w example-complex-routing
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Key Files
|
|
12
|
-
|
|
13
|
-
| File | Purpose |
|
|
14
|
-
|------|---------|
|
|
15
|
-
| `src/main.tsx` | Route tree assembly |
|
|
16
|
-
| `src/pages/__root.tsx` | Root layout with navigation |
|
|
17
|
-
| `src/pages/home.tsx` | Index route |
|
|
18
|
-
| `src/pages/posts/` | Nested routes with loader |
|
|
19
|
-
|
|
20
|
-
## What It Demonstrates
|
|
21
|
-
|
|
22
|
-
- Dynamic route params (`$postId`)
|
|
23
|
-
- Pathless layout routes
|
|
24
|
-
- Route loaders with `queryClient.ensureQueryData`
|
|
25
|
-
- Search params with `validateSearch`
|
|
26
|
-
- Catch-all 404 route
|
|
27
|
-
- Type-safe `route.useParams()`
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "example-complex-routing",
|
|
3
|
-
"version": "0.0.1-rc.18",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "ev dev",
|
|
7
|
-
"build": "ev build",
|
|
8
|
-
"check-types": "tsc --noEmit"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@evjs/runtime": "*",
|
|
12
|
-
"react": "^19.2.4",
|
|
13
|
-
"react-dom": "^19.2.4"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@evjs/cli": "*",
|
|
17
|
-
"@types/react": "^19.0.8",
|
|
18
|
-
"@types/react-dom": "^19.0.3",
|
|
19
|
-
"typescript": "^5.7.3"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
|
|
3
|
-
// ── Mock data ──
|
|
4
|
-
|
|
5
|
-
const posts = [
|
|
6
|
-
{
|
|
7
|
-
id: "1",
|
|
8
|
-
title: "Getting Started with evjs",
|
|
9
|
-
body: "evjs is a zero-config React meta-framework...",
|
|
10
|
-
author: "alice",
|
|
11
|
-
tags: ["intro", "tutorial"],
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
id: "2",
|
|
15
|
-
title: "Server Functions Deep Dive",
|
|
16
|
-
body: 'Server functions use the "use server" directive...',
|
|
17
|
-
author: "bob",
|
|
18
|
-
tags: ["server", "advanced"],
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "3",
|
|
22
|
-
title: "Routing Patterns",
|
|
23
|
-
body: "evjs uses TanStack Router for type-safe routing...",
|
|
24
|
-
author: "alice",
|
|
25
|
-
tags: ["routing", "tutorial"],
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: "4",
|
|
29
|
-
title: "WebSocket Transport",
|
|
30
|
-
body: "You can swap HTTP for WebSocket transport...",
|
|
31
|
-
author: "charlie",
|
|
32
|
-
tags: ["transport", "advanced"],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: "5",
|
|
36
|
-
title: "Deploying to Edge",
|
|
37
|
-
body: "Run your evjs app on Deno, Bun, or Workers...",
|
|
38
|
-
author: "bob",
|
|
39
|
-
tags: ["deploy", "edge"],
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const users: Record<string, { name: string; bio: string }> = {
|
|
44
|
-
alice: { name: "Alice", bio: "Core contributor" },
|
|
45
|
-
bob: { name: "Bob", bio: "Technical writer" },
|
|
46
|
-
charlie: { name: "Charlie", bio: "DevOps engineer" },
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// ── Server functions ──
|
|
50
|
-
|
|
51
|
-
export async function getPosts(query?: string) {
|
|
52
|
-
await delay(50);
|
|
53
|
-
if (!query) return posts;
|
|
54
|
-
const q = query.toLowerCase();
|
|
55
|
-
return posts.filter(
|
|
56
|
-
(p) =>
|
|
57
|
-
p.title.toLowerCase().includes(q) || p.tags.some((t) => t.includes(q)),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function getPost(id: string) {
|
|
62
|
-
await delay(50);
|
|
63
|
-
const post = posts.find((p) => p.id === id);
|
|
64
|
-
if (!post) throw new Error(`Post ${id} not found`);
|
|
65
|
-
return post;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function getUser(username: string) {
|
|
69
|
-
await delay(50);
|
|
70
|
-
const user = users[username];
|
|
71
|
-
if (!user) throw new Error(`User ${username} not found`);
|
|
72
|
-
return { username, ...user };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function getStats() {
|
|
76
|
-
await delay(50);
|
|
77
|
-
return {
|
|
78
|
-
totalPosts: posts.length,
|
|
79
|
-
totalUsers: Object.keys(users).length,
|
|
80
|
-
tags: [...new Set(posts.flatMap((p) => p.tags))],
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function delay(ms: number) {
|
|
85
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
86
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { createApp } from "@evjs/runtime/client";
|
|
2
|
-
import { rootRoute } from "./pages/__root";
|
|
3
|
-
import { notFoundRoute, redirectRoute } from "./pages/catch";
|
|
4
|
-
import { dashboardLayout, dashboardRoute } from "./pages/dashboard";
|
|
5
|
-
import { homeRoute } from "./pages/home";
|
|
6
|
-
import { postDetailRoute, postsIndexRoute, postsRoute } from "./pages/posts";
|
|
7
|
-
import { searchRoute } from "./pages/search";
|
|
8
|
-
import { userRoute } from "./pages/user";
|
|
9
|
-
|
|
10
|
-
const routeTree = rootRoute.addChildren([
|
|
11
|
-
homeRoute,
|
|
12
|
-
postsRoute.addChildren([postsIndexRoute, postDetailRoute]),
|
|
13
|
-
userRoute,
|
|
14
|
-
dashboardLayout.addChildren([dashboardRoute]),
|
|
15
|
-
searchRoute,
|
|
16
|
-
redirectRoute,
|
|
17
|
-
notFoundRoute,
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
const app = createApp({ routeTree });
|
|
21
|
-
|
|
22
|
-
// Register router type for full IDE type-safety on useParams, useSearch, Link, etc.
|
|
23
|
-
declare module "@tanstack/react-router" {
|
|
24
|
-
interface Register {
|
|
25
|
-
router: typeof app.router;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
app.render("#app");
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { createAppRootRoute, Link, Outlet } from "@evjs/runtime/client";
|
|
2
|
-
|
|
3
|
-
const styles = {
|
|
4
|
-
app: {
|
|
5
|
-
fontFamily: "system-ui, sans-serif",
|
|
6
|
-
maxWidth: 900,
|
|
7
|
-
margin: "0 auto",
|
|
8
|
-
padding: "1rem",
|
|
9
|
-
},
|
|
10
|
-
nav: {
|
|
11
|
-
display: "flex",
|
|
12
|
-
gap: "1rem",
|
|
13
|
-
padding: "0.75rem 0",
|
|
14
|
-
borderBottom: "1px solid #e5e7eb",
|
|
15
|
-
marginBottom: "1rem",
|
|
16
|
-
},
|
|
17
|
-
link: { textDecoration: "none", color: "#6b7280" },
|
|
18
|
-
activeLink: { color: "#111", fontWeight: 600 as const },
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
function RootLayout() {
|
|
22
|
-
return (
|
|
23
|
-
<div style={styles.app}>
|
|
24
|
-
<nav style={styles.nav}>
|
|
25
|
-
<Link
|
|
26
|
-
to="/"
|
|
27
|
-
style={styles.link}
|
|
28
|
-
activeProps={{ style: styles.activeLink }}
|
|
29
|
-
>
|
|
30
|
-
Home
|
|
31
|
-
</Link>
|
|
32
|
-
<Link
|
|
33
|
-
to="/posts"
|
|
34
|
-
style={styles.link}
|
|
35
|
-
activeProps={{ style: styles.activeLink }}
|
|
36
|
-
>
|
|
37
|
-
Posts
|
|
38
|
-
</Link>
|
|
39
|
-
<Link
|
|
40
|
-
to="/dashboard"
|
|
41
|
-
style={styles.link}
|
|
42
|
-
activeProps={{ style: styles.activeLink }}
|
|
43
|
-
>
|
|
44
|
-
Dashboard
|
|
45
|
-
</Link>
|
|
46
|
-
<Link
|
|
47
|
-
to="/search"
|
|
48
|
-
search={{ q: "" }}
|
|
49
|
-
style={styles.link}
|
|
50
|
-
activeProps={{ style: styles.activeLink }}
|
|
51
|
-
>
|
|
52
|
-
Search
|
|
53
|
-
</Link>
|
|
54
|
-
</nav>
|
|
55
|
-
<Outlet />
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const rootRoute = createAppRootRoute({ component: RootLayout });
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createRoute, Link, redirect } from "@evjs/runtime/client";
|
|
2
|
-
import { rootRoute } from "./__root";
|
|
3
|
-
|
|
4
|
-
// ── Redirect: /old-blog → /posts ──
|
|
5
|
-
|
|
6
|
-
export const redirectRoute = createRoute({
|
|
7
|
-
getParentRoute: () => rootRoute,
|
|
8
|
-
path: "/old-blog",
|
|
9
|
-
beforeLoad: () => {
|
|
10
|
-
throw redirect({ to: "/posts" });
|
|
11
|
-
},
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// ── 404 Catch-all ──
|
|
15
|
-
|
|
16
|
-
export const notFoundRoute = createRoute({
|
|
17
|
-
getParentRoute: () => rootRoute,
|
|
18
|
-
path: "*",
|
|
19
|
-
component: () => (
|
|
20
|
-
<div style={{ textAlign: "center", padding: "3rem" }}>
|
|
21
|
-
<h1 style={{ fontSize: 48 }}>404</h1>
|
|
22
|
-
<p style={{ color: "#6b7280" }}>Page not found</p>
|
|
23
|
-
<Link to="/">← Go home</Link>
|
|
24
|
-
</div>
|
|
25
|
-
),
|
|
26
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { createRoute, Outlet, query } from "@evjs/runtime/client";
|
|
2
|
-
import { getStats } from "../api/data.server";
|
|
3
|
-
import { rootRoute } from "./__root";
|
|
4
|
-
|
|
5
|
-
const styles = {
|
|
6
|
-
card: {
|
|
7
|
-
border: "1px solid #e5e7eb",
|
|
8
|
-
borderRadius: 8,
|
|
9
|
-
padding: "1rem",
|
|
10
|
-
marginBottom: "0.75rem",
|
|
11
|
-
},
|
|
12
|
-
stat: { textAlign: "center" as const, padding: "1rem" },
|
|
13
|
-
tag: {
|
|
14
|
-
display: "inline-block",
|
|
15
|
-
background: "#f3f4f6",
|
|
16
|
-
borderRadius: 4,
|
|
17
|
-
padding: "2px 8px",
|
|
18
|
-
fontSize: 12,
|
|
19
|
-
marginRight: 4,
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// ── Pathless layout (no URL segment, just shared UI) ──
|
|
24
|
-
|
|
25
|
-
export const dashboardLayout = createRoute({
|
|
26
|
-
getParentRoute: () => rootRoute,
|
|
27
|
-
id: "dashboard-layout",
|
|
28
|
-
component: () => (
|
|
29
|
-
<div>
|
|
30
|
-
<Outlet />
|
|
31
|
-
</div>
|
|
32
|
-
),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// ── Dashboard page (/dashboard) ──
|
|
36
|
-
|
|
37
|
-
function Dashboard() {
|
|
38
|
-
const { data: stats } = query(getStats).useQuery();
|
|
39
|
-
if (!stats) return <p>Loading...</p>;
|
|
40
|
-
return (
|
|
41
|
-
<div>
|
|
42
|
-
<h2>Dashboard</h2>
|
|
43
|
-
<div style={{ display: "flex", gap: "1rem" }}>
|
|
44
|
-
<div style={{ ...styles.card, ...styles.stat }}>
|
|
45
|
-
<div style={{ fontSize: 32, fontWeight: 700 }}>
|
|
46
|
-
{stats.totalPosts}
|
|
47
|
-
</div>
|
|
48
|
-
<div style={{ color: "#6b7280" }}>Posts</div>
|
|
49
|
-
</div>
|
|
50
|
-
<div style={{ ...styles.card, ...styles.stat }}>
|
|
51
|
-
<div style={{ fontSize: 32, fontWeight: 700 }}>
|
|
52
|
-
{stats.totalUsers}
|
|
53
|
-
</div>
|
|
54
|
-
<div style={{ color: "#6b7280" }}>Users</div>
|
|
55
|
-
</div>
|
|
56
|
-
<div style={{ ...styles.card, ...styles.stat }}>
|
|
57
|
-
<div style={{ fontSize: 32, fontWeight: 700 }}>
|
|
58
|
-
{stats.tags.length}
|
|
59
|
-
</div>
|
|
60
|
-
<div style={{ color: "#6b7280" }}>Tags</div>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
<h3>All Tags</h3>
|
|
64
|
-
<div>
|
|
65
|
-
{stats.tags.map((t) => (
|
|
66
|
-
<span key={t} style={styles.tag}>
|
|
67
|
-
{t}
|
|
68
|
-
</span>
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const dashboardRoute = createRoute({
|
|
76
|
-
getParentRoute: () => dashboardLayout,
|
|
77
|
-
path: "/dashboard",
|
|
78
|
-
loader: ({ context }) =>
|
|
79
|
-
context.queryClient.ensureQueryData(query(getStats).queryOptions()),
|
|
80
|
-
component: Dashboard,
|
|
81
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { createRoute, Link } from "@evjs/runtime/client";
|
|
2
|
-
import { rootRoute } from "./__root";
|
|
3
|
-
|
|
4
|
-
export const homeRoute = createRoute({
|
|
5
|
-
getParentRoute: () => rootRoute,
|
|
6
|
-
path: "/",
|
|
7
|
-
component: () => (
|
|
8
|
-
<div>
|
|
9
|
-
<h1>evjs Complex Routing Example</h1>
|
|
10
|
-
<p>
|
|
11
|
-
Demonstrates nested layouts, dynamic params, search params, redirects,
|
|
12
|
-
and server function loaders.
|
|
13
|
-
</p>
|
|
14
|
-
<ul>
|
|
15
|
-
<li>
|
|
16
|
-
<Link to="/posts">Posts</Link> — nested group with dynamic{" "}
|
|
17
|
-
<code>$postId</code>
|
|
18
|
-
</li>
|
|
19
|
-
<li>
|
|
20
|
-
<Link to="/dashboard">Dashboard</Link> — pathless layout with server
|
|
21
|
-
function loader
|
|
22
|
-
</li>
|
|
23
|
-
<li>
|
|
24
|
-
<Link to="/search" search={{ q: "tutorial" }}>
|
|
25
|
-
Search
|
|
26
|
-
</Link>{" "}
|
|
27
|
-
— search params
|
|
28
|
-
</li>
|
|
29
|
-
<li>
|
|
30
|
-
<Link to="/old-blog">Old Blog</Link> — redirect to /posts
|
|
31
|
-
</li>
|
|
32
|
-
</ul>
|
|
33
|
-
</div>
|
|
34
|
-
),
|
|
35
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { createRoute, Link, Outlet, query } from "@evjs/runtime/client";
|
|
2
|
-
import { getPost, getPosts } from "../../api/data.server";
|
|
3
|
-
import { rootRoute } from "../__root";
|
|
4
|
-
|
|
5
|
-
const styles = {
|
|
6
|
-
sidebar: { display: "flex", gap: "1.5rem" },
|
|
7
|
-
nav: {
|
|
8
|
-
minWidth: 180,
|
|
9
|
-
borderRight: "1px solid #e5e7eb",
|
|
10
|
-
paddingRight: "1rem",
|
|
11
|
-
},
|
|
12
|
-
tag: {
|
|
13
|
-
display: "inline-block",
|
|
14
|
-
background: "#f3f4f6",
|
|
15
|
-
borderRadius: 4,
|
|
16
|
-
padding: "2px 8px",
|
|
17
|
-
fontSize: 12,
|
|
18
|
-
marginRight: 4,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// ── Posts layout (/posts) ──
|
|
23
|
-
|
|
24
|
-
function PostsLayout() {
|
|
25
|
-
const { data: posts } = query(getPosts).useQuery();
|
|
26
|
-
return (
|
|
27
|
-
<div style={styles.sidebar}>
|
|
28
|
-
<div style={styles.nav}>
|
|
29
|
-
<h3 style={{ marginTop: 0 }}>Posts</h3>
|
|
30
|
-
<ul style={{ listStyle: "none", padding: 0 }}>
|
|
31
|
-
{posts?.map((p) => (
|
|
32
|
-
<li key={p.id} style={{ marginBottom: "0.25rem" }}>
|
|
33
|
-
<Link
|
|
34
|
-
to="/posts/$postId"
|
|
35
|
-
params={{ postId: p.id }}
|
|
36
|
-
style={{ textDecoration: "none", color: "#374151" }}
|
|
37
|
-
activeProps={{ style: { fontWeight: "bold", color: "#111" } }}
|
|
38
|
-
>
|
|
39
|
-
{p.title}
|
|
40
|
-
</Link>
|
|
41
|
-
</li>
|
|
42
|
-
))}
|
|
43
|
-
</ul>
|
|
44
|
-
</div>
|
|
45
|
-
<div style={{ flex: 1 }}>
|
|
46
|
-
<Outlet />
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const postsRoute = createRoute({
|
|
53
|
-
getParentRoute: () => rootRoute,
|
|
54
|
-
path: "/posts",
|
|
55
|
-
component: PostsLayout,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ── Posts index (/posts/) ──
|
|
59
|
-
|
|
60
|
-
export const postsIndexRoute = createRoute({
|
|
61
|
-
getParentRoute: () => postsRoute,
|
|
62
|
-
path: "/",
|
|
63
|
-
component: () => (
|
|
64
|
-
<p style={{ color: "#6b7280" }}>← Select a post from the sidebar</p>
|
|
65
|
-
),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// ── Post detail (/posts/$postId) ──
|
|
69
|
-
|
|
70
|
-
function PostDetail() {
|
|
71
|
-
const { postId } = postDetailRoute.useParams();
|
|
72
|
-
const { data: post } = query(getPost).useQuery(postId);
|
|
73
|
-
|
|
74
|
-
if (!post) return <p>Loading...</p>;
|
|
75
|
-
return (
|
|
76
|
-
<div>
|
|
77
|
-
<h2>{post.title}</h2>
|
|
78
|
-
<p style={{ color: "#6b7280" }}>
|
|
79
|
-
by{" "}
|
|
80
|
-
<Link to="/users/$username" params={{ username: post.author }}>
|
|
81
|
-
{post.author}
|
|
82
|
-
</Link>
|
|
83
|
-
</p>
|
|
84
|
-
<p>{post.body}</p>
|
|
85
|
-
<div>
|
|
86
|
-
{post.tags.map((tag) => (
|
|
87
|
-
<span key={tag} style={styles.tag}>
|
|
88
|
-
{tag}
|
|
89
|
-
</span>
|
|
90
|
-
))}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export const postDetailRoute = createRoute({
|
|
97
|
-
getParentRoute: () => postsRoute,
|
|
98
|
-
path: "$postId",
|
|
99
|
-
loader: ({ params, context }) =>
|
|
100
|
-
context.queryClient.ensureQueryData(
|
|
101
|
-
query(getPost).queryOptions(params.postId),
|
|
102
|
-
),
|
|
103
|
-
component: PostDetail,
|
|
104
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { createRoute, Link, query } from "@evjs/runtime/client";
|
|
2
|
-
import { getPosts } from "../api/data.server";
|
|
3
|
-
import { rootRoute } from "./__root";
|
|
4
|
-
|
|
5
|
-
const styles = {
|
|
6
|
-
card: {
|
|
7
|
-
border: "1px solid #e5e7eb",
|
|
8
|
-
borderRadius: 8,
|
|
9
|
-
padding: "1rem",
|
|
10
|
-
marginBottom: "0.75rem",
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function SearchPage() {
|
|
15
|
-
const { q } = searchRoute.useSearch();
|
|
16
|
-
const { data: results } = query(getPosts).useQuery(q || undefined);
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div>
|
|
20
|
-
<h2>Search</h2>
|
|
21
|
-
<form
|
|
22
|
-
onSubmit={(e) => {
|
|
23
|
-
e.preventDefault();
|
|
24
|
-
const form = new FormData(e.currentTarget);
|
|
25
|
-
const q = form.get("q") as string;
|
|
26
|
-
window.location.search = `?q=${encodeURIComponent(q)}`;
|
|
27
|
-
}}
|
|
28
|
-
>
|
|
29
|
-
<input
|
|
30
|
-
name="q"
|
|
31
|
-
defaultValue={q}
|
|
32
|
-
placeholder="Search posts..."
|
|
33
|
-
style={{
|
|
34
|
-
padding: "0.5rem",
|
|
35
|
-
width: 300,
|
|
36
|
-
borderRadius: 4,
|
|
37
|
-
border: "1px solid #d1d5db",
|
|
38
|
-
}}
|
|
39
|
-
/>
|
|
40
|
-
</form>
|
|
41
|
-
<div style={{ marginTop: "1rem" }}>
|
|
42
|
-
{q && <p style={{ color: "#6b7280" }}>Results for "{q}":</p>}
|
|
43
|
-
{results?.map((post) => (
|
|
44
|
-
<div key={post.id} style={styles.card}>
|
|
45
|
-
<Link
|
|
46
|
-
to="/posts/$postId"
|
|
47
|
-
params={{ postId: post.id }}
|
|
48
|
-
style={{ textDecoration: "none", color: "#111" }}
|
|
49
|
-
>
|
|
50
|
-
<strong>{post.title}</strong>
|
|
51
|
-
</Link>
|
|
52
|
-
<p
|
|
53
|
-
style={{ margin: "0.25rem 0 0", color: "#6b7280", fontSize: 14 }}
|
|
54
|
-
>
|
|
55
|
-
{post.body}
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
))}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const searchRoute = createRoute({
|
|
65
|
-
getParentRoute: () => rootRoute,
|
|
66
|
-
path: "/search",
|
|
67
|
-
validateSearch: (search: Record<string, unknown>) => ({
|
|
68
|
-
q: (search.q as string) || "",
|
|
69
|
-
}),
|
|
70
|
-
component: SearchPage,
|
|
71
|
-
});
|