@evjs/cli 0.0.1-rc.13

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.
Files changed (40) hide show
  1. package/AGENT.md +121 -0
  2. package/README.md +67 -0
  3. package/dist/config.js +35 -0
  4. package/dist/create-webpack-config.js +99 -0
  5. package/dist/index.js +220 -0
  6. package/dist/load-config.js +39 -0
  7. package/package.json +75 -0
  8. package/templates/basic-csr/index.html +11 -0
  9. package/templates/basic-csr/package.json +21 -0
  10. package/templates/basic-csr/src/main.tsx +22 -0
  11. package/templates/basic-csr/src/pages/__root.tsx +28 -0
  12. package/templates/basic-csr/src/pages/about.tsx +19 -0
  13. package/templates/basic-csr/src/pages/home.tsx +19 -0
  14. package/templates/basic-csr/src/pages/posts/index.tsx +63 -0
  15. package/templates/basic-csr/tsconfig.json +17 -0
  16. package/templates/basic-server-fns/index.html +11 -0
  17. package/templates/basic-server-fns/package.json +21 -0
  18. package/templates/basic-server-fns/src/api/users.server.ts +31 -0
  19. package/templates/basic-server-fns/src/main.tsx +12 -0
  20. package/templates/basic-server-fns/src/routes.tsx +108 -0
  21. package/templates/basic-server-fns/tsconfig.json +16 -0
  22. package/templates/complex-routing/index.html +11 -0
  23. package/templates/complex-routing/package.json +21 -0
  24. package/templates/complex-routing/src/api/data.server.ts +86 -0
  25. package/templates/complex-routing/src/main.tsx +29 -0
  26. package/templates/complex-routing/src/pages/__root.tsx +60 -0
  27. package/templates/complex-routing/src/pages/catch.tsx +26 -0
  28. package/templates/complex-routing/src/pages/dashboard.tsx +81 -0
  29. package/templates/complex-routing/src/pages/home.tsx +35 -0
  30. package/templates/complex-routing/src/pages/posts/index.tsx +104 -0
  31. package/templates/complex-routing/src/pages/search.tsx +71 -0
  32. package/templates/complex-routing/src/pages/user.tsx +32 -0
  33. package/templates/complex-routing/tsconfig.json +13 -0
  34. package/templates/configured-server-fns/ev.config.ts +56 -0
  35. package/templates/configured-server-fns/index.html +11 -0
  36. package/templates/configured-server-fns/package.json +21 -0
  37. package/templates/configured-server-fns/src/api/users.server.ts +22 -0
  38. package/templates/configured-server-fns/src/main.tsx +12 -0
  39. package/templates/configured-server-fns/src/routes.tsx +111 -0
  40. package/templates/configured-server-fns/tsconfig.json +16 -0
@@ -0,0 +1,81 @@
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
+ });
@@ -0,0 +1,35 @@
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
+ });
@@ -0,0 +1,104 @@
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
+ });
@@ -0,0 +1,71 @@
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
+ });
@@ -0,0 +1,32 @@
1
+ import { createRoute, Link, query } from "@evjs/runtime/client";
2
+ import { getUser } from "../api/data.server";
3
+ import { rootRoute } from "./__root";
4
+
5
+ const styles = {
6
+ card: { border: "1px solid #e5e7eb", borderRadius: 8, padding: "1rem" },
7
+ };
8
+
9
+ function UserProfile() {
10
+ const { username } = userRoute.useParams();
11
+ const { data: user } = query(getUser).useQuery(username);
12
+
13
+ if (!user) return <p>Loading...</p>;
14
+ return (
15
+ <div style={styles.card}>
16
+ <h2>{user.name}</h2>
17
+ <p style={{ color: "#6b7280" }}>@{user.username}</p>
18
+ <p>{user.bio}</p>
19
+ <Link to="/posts">← Back to posts</Link>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ export const userRoute = createRoute({
25
+ getParentRoute: () => rootRoute,
26
+ path: "/users/$username",
27
+ loader: ({ params, context }) =>
28
+ context.queryClient.ensureQueryData(
29
+ query(getUser).queryOptions(params.username),
30
+ ),
31
+ component: UserProfile,
32
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "dist"
11
+ },
12
+ "include": ["src"]
13
+ }
@@ -0,0 +1,56 @@
1
+ import { defineConfig } from "@evjs/cli";
2
+
3
+ /**
4
+ * Advanced ev.config.ts example.
5
+ *
6
+ * This file demonstrates all available configuration options.
7
+ * All fields are optional — evjs works zero-config out of the box.
8
+ */
9
+ export default defineConfig({
10
+ client: {
11
+ // Client entry point (default: "./src/main.tsx")
12
+ entry: "./src/main.tsx",
13
+
14
+ // HTML template (default: "./index.html")
15
+ html: "./index.html",
16
+
17
+ // Dev server options (merged with built-in defaults)
18
+ dev: {
19
+ // Dev server port (default: 3000)
20
+ port: 4000,
21
+
22
+ // Any dev server options can be added here:
23
+ // https: true,
24
+ // open: true,
25
+ // historyApiFallback: true,
26
+ },
27
+
28
+ // Transport configuration for server function calls
29
+ transport: {
30
+ // Base URL for server function endpoint (default: same origin)
31
+ // baseUrl: "https://api.example.com",
32
+
33
+ // Server function endpoint path (default: "/api/fn")
34
+ endpoint: "/api/fn",
35
+ },
36
+ },
37
+
38
+ server: {
39
+ // Server function endpoint path (default: "/api/fn")
40
+ endpoint: "/api/fn",
41
+
42
+ // Server runner module (default: "@evjs/runtime/server/node")
43
+ // For Deno/Bun/Workers, use: "@evjs/runtime/server/ecma"
44
+ runner: "@evjs/runtime/server/node",
45
+
46
+ // Middleware module paths to auto-register in server entry
47
+ // These are imported and applied in order
48
+ // middleware: ["./src/middleware/auth.ts", "./src/middleware/cors.ts"],
49
+
50
+ // Dev server options
51
+ dev: {
52
+ // API server port in dev mode (default: 3001)
53
+ port: 4001,
54
+ },
55
+ },
56
+ });
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Configured Server Functions Example</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "example-configured-server-fns",
3
+ "version": "0.0.1-rc.8",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "ev dev",
7
+ "build": "ev build",
8
+ "check-types": "tsc --noEmit"
9
+ },
10
+ "dependencies": {
11
+ "@evjs/runtime": "^0.0.1-rc.13",
12
+ "react": "^19.2.4",
13
+ "react-dom": "^19.2.4"
14
+ },
15
+ "devDependencies": {
16
+ "@evjs/cli": "^0.0.1-rc.5",
17
+ "@types/react": "^19.1.8",
18
+ "@types/react-dom": "^19.1.8",
19
+ "typescript": "^5.7.3"
20
+ }
21
+ }
@@ -0,0 +1,22 @@
1
+ "use server";
2
+
3
+ /** Simulated user database. */
4
+ const users = [
5
+ { id: "1", name: "Alice", email: "alice@example.com" },
6
+ { id: "2", name: "Bob", email: "bob@example.com" },
7
+ { id: "3", name: "Charlie", email: "charlie@example.com" },
8
+ ];
9
+
10
+ /** Get all users. */
11
+ export async function getUsers() {
12
+ await new Promise((r) => setTimeout(r, 100));
13
+ return users;
14
+ }
15
+
16
+ /** Create a new user. */
17
+ export async function createUser(data: { name: string; email: string }) {
18
+ await new Promise((r) => setTimeout(r, 50));
19
+ const newUser = { id: String(users.length + 1), ...data };
20
+ users.push(newUser);
21
+ return newUser;
22
+ }
@@ -0,0 +1,12 @@
1
+ import { createApp } from "@evjs/runtime/client";
2
+ import { routeTree } from "./routes";
3
+
4
+ const app = createApp({ routeTree });
5
+
6
+ declare module "@tanstack/react-router" {
7
+ interface Register {
8
+ router: typeof app.router;
9
+ }
10
+ }
11
+
12
+ app.render("#app");
@@ -0,0 +1,111 @@
1
+ import {
2
+ createAppRootRoute,
3
+ createMutationProxy,
4
+ createQueryProxy,
5
+ createRoute,
6
+ Link,
7
+ Outlet,
8
+ useQueryClient,
9
+ } from "@evjs/runtime/client";
10
+ import { useState } from "react";
11
+ import * as usersApi from "./api/users.server";
12
+
13
+ // ── API Proxy ──
14
+
15
+ const api = {
16
+ users: {
17
+ query: createQueryProxy(usersApi),
18
+ mutation: createMutationProxy(usersApi),
19
+ },
20
+ };
21
+
22
+ // ── Root Route ──
23
+
24
+ function Root() {
25
+ return (
26
+ <div style={{ fontFamily: "system-ui, sans-serif", padding: "1rem" }}>
27
+ <h1>Configured Server Functions</h1>
28
+ <p style={{ color: "#666" }}>
29
+ This example uses <code>ev.config.ts</code> for custom ports and
30
+ settings.
31
+ </p>
32
+ <nav style={{ display: "flex", gap: "1rem", marginBottom: "1rem" }}>
33
+ <Link to="/">Users</Link>
34
+ </nav>
35
+ <Outlet />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ const rootRoute = createAppRootRoute({ component: Root });
41
+
42
+ // ── Users Route ──
43
+
44
+ function UsersPage() {
45
+ const [name, setName] = useState("");
46
+ const [email, setEmail] = useState("");
47
+
48
+ const { data: users = [], isLoading } = api.users.query.getUsers.useQuery();
49
+
50
+ const queryClient = useQueryClient();
51
+ const { mutateAsync: doCreateUser } =
52
+ api.users.mutation.createUser.useMutation({
53
+ onSuccess: () => {
54
+ queryClient.invalidateQueries({
55
+ queryKey: api.users.query.getUsers.queryKey(),
56
+ });
57
+ },
58
+ });
59
+
60
+ async function handleCreate(e: { preventDefault: () => void }) {
61
+ e.preventDefault();
62
+ if (!name || !email) return;
63
+ await doCreateUser([{ name, email }]);
64
+ setName("");
65
+ setEmail("");
66
+ }
67
+
68
+ if (isLoading) return <p>Loading users from server…</p>;
69
+
70
+ return (
71
+ <div>
72
+ <h2>Users (via Query Proxy)</h2>
73
+ <ul>
74
+ {users.map((u: { id: string; name: string; email: string }) => (
75
+ <li key={u.id}>
76
+ {u.name} — {u.email}
77
+ </li>
78
+ ))}
79
+ </ul>
80
+
81
+ <h3>Add User</h3>
82
+ <form onSubmit={handleCreate} style={{ display: "flex", gap: "0.5rem" }}>
83
+ <input
84
+ placeholder="Name"
85
+ value={name}
86
+ onChange={(e) => setName(e.target.value)}
87
+ />
88
+ <input
89
+ placeholder="Email"
90
+ value={email}
91
+ onChange={(e) => setEmail(e.target.value)}
92
+ />
93
+ <button type="submit">Create</button>
94
+ </form>
95
+ </div>
96
+ );
97
+ }
98
+
99
+ const usersRoute = createRoute({
100
+ getParentRoute: () => rootRoute,
101
+ path: "/",
102
+ component: UsersPage,
103
+ loader: ({ context }) =>
104
+ context.queryClient.ensureQueryData(
105
+ api.users.query.getUsers.queryOptions(),
106
+ ),
107
+ });
108
+
109
+ // ── Route Tree ──
110
+
111
+ export const routeTree = rootRoute.addChildren([usersRoute]);
@@ -0,0 +1,16 @@
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
+ }