@evjs/cli 0.0.0

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 (39) hide show
  1. package/README.md +99 -0
  2. package/dist/config.js +35 -0
  3. package/dist/create-webpack-config.js +99 -0
  4. package/dist/index.js +218 -0
  5. package/dist/load-config.js +39 -0
  6. package/package.json +74 -0
  7. package/templates/basic-csr/index.html +11 -0
  8. package/templates/basic-csr/package.json +21 -0
  9. package/templates/basic-csr/src/main.tsx +22 -0
  10. package/templates/basic-csr/src/pages/__root.tsx +28 -0
  11. package/templates/basic-csr/src/pages/about.tsx +19 -0
  12. package/templates/basic-csr/src/pages/home.tsx +19 -0
  13. package/templates/basic-csr/src/pages/posts/index.tsx +63 -0
  14. package/templates/basic-csr/tsconfig.json +17 -0
  15. package/templates/basic-server-fns/index.html +11 -0
  16. package/templates/basic-server-fns/package.json +21 -0
  17. package/templates/basic-server-fns/src/api/users.server.ts +31 -0
  18. package/templates/basic-server-fns/src/main.tsx +12 -0
  19. package/templates/basic-server-fns/src/routes.tsx +108 -0
  20. package/templates/basic-server-fns/tsconfig.json +16 -0
  21. package/templates/complex-routing/index.html +11 -0
  22. package/templates/complex-routing/package.json +21 -0
  23. package/templates/complex-routing/src/api/data.server.ts +86 -0
  24. package/templates/complex-routing/src/main.tsx +29 -0
  25. package/templates/complex-routing/src/pages/__root.tsx +60 -0
  26. package/templates/complex-routing/src/pages/catch.tsx +26 -0
  27. package/templates/complex-routing/src/pages/dashboard.tsx +81 -0
  28. package/templates/complex-routing/src/pages/home.tsx +35 -0
  29. package/templates/complex-routing/src/pages/posts/index.tsx +104 -0
  30. package/templates/complex-routing/src/pages/search.tsx +71 -0
  31. package/templates/complex-routing/src/pages/user.tsx +32 -0
  32. package/templates/complex-routing/tsconfig.json +13 -0
  33. package/templates/configured-server-fns/ev.config.ts +56 -0
  34. package/templates/configured-server-fns/index.html +11 -0
  35. package/templates/configured-server-fns/package.json +21 -0
  36. package/templates/configured-server-fns/src/api/users.server.ts +22 -0
  37. package/templates/configured-server-fns/src/main.tsx +12 -0
  38. package/templates/configured-server-fns/src/routes.tsx +111 -0
  39. package/templates/configured-server-fns/tsconfig.json +16 -0
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @evjs/cli
2
+
3
+ > CLI and configuration for the **@evjs/cli** meta-framework.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @evjs/cli
9
+ ```
10
+
11
+ ## Zero-Config
12
+
13
+ No configuration file is needed. `ev dev` and `ev build` work out of the box with sensible defaults:
14
+
15
+ - Entry: `./src/main.tsx`
16
+ - HTML: `./index.html`
17
+ - Client dev server: port 3000
18
+ - API server (dev): port 3001
19
+ - Server functions auto-discovered via `"use server"` directive
20
+
21
+ ## Commands
22
+
23
+ | Command | Description |
24
+ |---------|-------------|
25
+ | `ev init [name]` | Scaffold a new project from a template |
26
+ | `ev dev` | Start dev server (client HMR + API watch) |
27
+ | `ev build` | Production build (client + server) |
28
+
29
+ ### `ev init [name]`
30
+
31
+ Templates: `basic-csr`, `basic-server-fns`, `trpc-server-fns`.
32
+ Option: `-t, --template <template>` to skip interactive selection.
33
+
34
+ ### `ev dev`
35
+
36
+ Uses webpack Node API directly (no temp config files):
37
+ 1. **WebpackDevServer** (port 3000) — client bundle with HMR.
38
+ 2. **Node API Server** (port 3001) — auto-starts when server bundle is emitted, uses `node --watch`.
39
+
40
+ ### `ev build`
41
+
42
+ Runs webpack via Node API with `NODE_ENV=production`:
43
+ - `dist/client/` — optimized client assets with content hashes.
44
+ - `dist/server/main.[hash].js` — server bundle (entry discovered via `dist/manifest.json`).
45
+
46
+ ## Configuration
47
+
48
+ Create `ev.config.ts` in the project root (optional):
49
+
50
+ ```ts
51
+ import { defineConfig } from "@evjs/cli";
52
+
53
+ export default defineConfig({
54
+ client: {
55
+ entry: "./src/main.tsx",
56
+ html: "./index.html",
57
+ dev: { port: 3000 },
58
+ },
59
+ server: {
60
+ endpoint: "/api/fn",
61
+ middleware: [],
62
+ dev: { port: 3001 },
63
+ },
64
+ });
65
+ ```
66
+
67
+ The `client.dev` and `server.dev` fields accept extra options that are merged with defaults.
68
+
69
+ ## Project Structure
70
+
71
+ ```
72
+ my-app/
73
+ ├── ev.config.ts # optional config
74
+ ├── index.html # HTML template
75
+ ├── package.json
76
+ ├── tsconfig.json
77
+ └── src/
78
+ ├── main.tsx # app bootstrap (keep minimal)
79
+ ├── routes.tsx # route tree + components
80
+ ├── api/ # server functions
81
+ │ ├── users.server.ts
82
+ │ └── posts.server.ts
83
+ └── middleware/ # server middleware (optional)
84
+ └── auth.ts
85
+ ```
86
+
87
+ ## Common Mistakes
88
+
89
+ 1. **Don't create `webpack.config.cjs`** — use `ev.config.ts` instead
90
+ 2. **Don't install webpack manually** — it's a dependency of `@evjs/cli`
91
+ 3. **Config file must be `ev.config.ts`** — not `evjs.config.ts`
92
+ 4. **Import `defineConfig` from `@evjs/cli`** — not from `@evjs/runtime`
93
+
94
+ ## Bundled Dependencies
95
+
96
+ Users do NOT need to install these — they're included in `@evjs/cli`:
97
+ - `webpack`, `webpack-dev-server`
98
+ - `html-webpack-plugin`, `swc-loader`, `@swc/core`
99
+ - `@evjs/webpack-plugin`, `@evjs/build-tools`
package/dist/config.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Default configuration values.
3
+ *
4
+ * Single source of truth for all defaults across the framework.
5
+ */
6
+ export const CONFIG_DEFAULTS = {
7
+ entry: "./src/main.tsx",
8
+ html: "./index.html",
9
+ clientPort: 3000,
10
+ serverPort: 3001,
11
+ endpoint: "/api/fn",
12
+ };
13
+ /**
14
+ * Define configuration for the evjs framework.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * // ev.config.ts
19
+ * import { defineConfig } from "@evjs/cli";
20
+ *
21
+ * export default defineConfig({
22
+ * client: {
23
+ * entry: "./src/main.tsx",
24
+ * dev: { port: 3000 },
25
+ * },
26
+ * server: {
27
+ * endpoint: "/api/fn",
28
+ * dev: { port: 3001 },
29
+ * },
30
+ * });
31
+ * ```
32
+ */
33
+ export function defineConfig(config) {
34
+ return config;
35
+ }
@@ -0,0 +1,99 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { CONFIG_DEFAULTS } from "./config.js";
4
+ const esmRequire = createRequire(import.meta.url);
5
+ /**
6
+ * Create a webpack configuration object from EvfConfig.
7
+ *
8
+ * Returns a plain object that can be passed directly to the webpack Node API.
9
+ * No temp files are generated.
10
+ */
11
+ export function createWebpackConfig(config, cwd) {
12
+ const client = config?.client;
13
+ const server = config?.server;
14
+ const entry = client?.entry ?? CONFIG_DEFAULTS.entry;
15
+ const html = client?.html ?? CONFIG_DEFAULTS.html;
16
+ const clientPort = client?.dev?.port ?? CONFIG_DEFAULTS.clientPort;
17
+ const serverPort = server?.dev?.port ?? CONFIG_DEFAULTS.serverPort;
18
+ const endpoint = server?.endpoint ?? CONFIG_DEFAULTS.endpoint;
19
+ const isProduction = process.env.NODE_ENV === "production";
20
+ const HtmlWebpackPlugin = esmRequire("html-webpack-plugin");
21
+ const { EvWebpackPlugin } = esmRequire("@evjs/webpack-plugin");
22
+ const pluginOptions = server?.middleware?.length
23
+ ? { server: { middleware: server.middleware } }
24
+ : undefined;
25
+ // Resolve loader paths from evjs's dependency tree so they work
26
+ // even when the user's project doesn't list them as direct deps.
27
+ const resolveLoader = (id) => {
28
+ try {
29
+ return esmRequire.resolve(id);
30
+ }
31
+ catch {
32
+ return id;
33
+ }
34
+ };
35
+ // Derive the proxy base path from the configured endpoint.
36
+ // e.g. "/api/fn" → "/api", "/rpc/v1" → "/rpc"
37
+ const proxyBase = `/${endpoint.split("/").filter(Boolean)[0] || "api"}`;
38
+ // Destructure port out of dev overrides to avoid passing it twice.
39
+ const { port: _p, ...devServerOverrides } = client?.dev ?? {};
40
+ return {
41
+ name: "client",
42
+ mode: isProduction ? "production" : "development",
43
+ devtool: isProduction ? "hidden-source-map" : "source-map",
44
+ entry,
45
+ output: {
46
+ path: path.resolve(cwd, "dist/client"),
47
+ filename: isProduction ? "[name].[contenthash:8].js" : "index.js",
48
+ clean: true,
49
+ },
50
+ resolve: {
51
+ extensions: [".tsx", ".ts", ".js"],
52
+ },
53
+ module: {
54
+ rules: [
55
+ {
56
+ test: /\.m?js/,
57
+ resolve: { fullySpecified: false },
58
+ },
59
+ {
60
+ test: /\.tsx?$/,
61
+ exclude: /node_modules/,
62
+ use: [
63
+ {
64
+ loader: resolveLoader("swc-loader"),
65
+ options: {
66
+ jsc: {
67
+ parser: { syntax: "typescript", tsx: true },
68
+ transform: { react: { runtime: "automatic" } },
69
+ },
70
+ },
71
+ },
72
+ {
73
+ loader: resolveLoader("@evjs/webpack-plugin/server-fn-loader"),
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ },
79
+ plugins: [
80
+ new HtmlWebpackPlugin({ template: html }),
81
+ new EvWebpackPlugin(pluginOptions),
82
+ ],
83
+ optimization: isProduction
84
+ ? { splitChunks: { chunks: "all" } }
85
+ : undefined,
86
+ devServer: {
87
+ port: clientPort,
88
+ hot: true,
89
+ devMiddleware: { writeToDisk: true },
90
+ proxy: [
91
+ {
92
+ context: [proxyBase],
93
+ target: `http://localhost:${serverPort}`,
94
+ },
95
+ ],
96
+ ...devServerOverrides,
97
+ },
98
+ };
99
+ }
package/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { configure, getConsoleSink, getLogger } from "@logtape/logtape";
6
+ import { Command } from "commander";
7
+ import { execa } from "execa";
8
+ import fs from "fs-extra";
9
+ import prompts from "prompts";
10
+ import { CONFIG_DEFAULTS } from "./config.js";
11
+ const esmRequire = createRequire(import.meta.url);
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ await configure({
14
+ sinks: { console: getConsoleSink() },
15
+ loggers: [
16
+ { category: ["logtape", "meta"], lowestLevel: "warning" },
17
+ { category: ["evjs"], sinks: ["console"], lowestLevel: "info" },
18
+ ],
19
+ });
20
+ const logger = getLogger(["evjs", "cli"]);
21
+ const pkg = fs.readJsonSync(path.resolve(__dirname, "../package.json"));
22
+ const program = new Command();
23
+ program
24
+ .name("ev")
25
+ .description("CLI for the evjs framework")
26
+ .version(pkg.version);
27
+ program
28
+ .command("init")
29
+ .description("Initialize a new evjs project")
30
+ .argument("[name]", "Project name")
31
+ .option("-t, --template <template>", "Template to use")
32
+ .action(async (name, options) => {
33
+ const response = await prompts([
34
+ {
35
+ type: name ? null : "text",
36
+ name: "projectName",
37
+ message: "Project name:",
38
+ initial: name || "my-evjs-app",
39
+ },
40
+ {
41
+ type: options.template ? null : "select",
42
+ name: "template",
43
+ message: "Select a template:",
44
+ choices: [
45
+ { title: "Basic CSR (Client-Side Rendering)", value: "basic-csr" },
46
+ { title: "Basic Server Functions", value: "basic-server-fns" },
47
+ {
48
+ title: "Configured Server Functions (ev.config.ts + Query)",
49
+ value: "configured-server-fns",
50
+ },
51
+ {
52
+ title: "Complex Routing (params, search, layouts, loaders)",
53
+ value: "complex-routing",
54
+ },
55
+ ],
56
+ },
57
+ ], {
58
+ onCancel: () => {
59
+ process.exit(1);
60
+ },
61
+ });
62
+ const projectName = response.projectName || name;
63
+ const template = response.template || options.template;
64
+ const targetDir = path.resolve(process.cwd(), projectName);
65
+ if (fs.existsSync(targetDir)) {
66
+ logger.error `Directory ${projectName} already exists!`;
67
+ process.exit(1);
68
+ }
69
+ const templateDir = path.resolve(__dirname, "../templates", template);
70
+ if (!fs.existsSync(templateDir)) {
71
+ logger.error `Template ${template} not found!`;
72
+ process.exit(1);
73
+ }
74
+ logger.info `Scaffolding project in ${targetDir}...`;
75
+ await fs.copy(templateDir, targetDir, {
76
+ dereference: true,
77
+ filter: (src) => {
78
+ const basename = path.basename(src);
79
+ return !["node_modules", "dist", ".turbo"].includes(basename);
80
+ },
81
+ });
82
+ // Post-process package.json: sync @evjs/* versions and set project name
83
+ const pkgPath = path.join(targetDir, "package.json");
84
+ if (fs.existsSync(pkgPath)) {
85
+ const pkg = await fs.readJson(pkgPath);
86
+ pkg.name = projectName;
87
+ delete pkg.private; // Templates shouldn't be private by default
88
+ const updateDeps = (deps) => {
89
+ if (!deps)
90
+ return;
91
+ for (const [name, val] of Object.entries(deps)) {
92
+ // Sync all @evjs/* packages to current CLI version
93
+ if (name.startsWith("@evjs/") &&
94
+ (val === "*" ||
95
+ (typeof val === "string" && val.includes("workspace")))) {
96
+ deps[name] = `^${pkg.version}`;
97
+ }
98
+ }
99
+ };
100
+ updateDeps(pkg.dependencies);
101
+ updateDeps(pkg.devDependencies);
102
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
103
+ }
104
+ logger.info `Done! Now run:`;
105
+ logger.info ` cd ${projectName}`;
106
+ logger.info ` npm install`;
107
+ logger.info ` npm run dev`;
108
+ });
109
+ /**
110
+ * Load config and create webpack configuration object.
111
+ *
112
+ * Uses ev.config.ts when present, otherwise falls back to zero-config defaults.
113
+ * No webpack.config.cjs fallback — the meta-framework owns the build config.
114
+ */
115
+ async function resolveWebpackConfig(cwd) {
116
+ const { loadConfig } = await import("./load-config.js");
117
+ const evjsConfig = await loadConfig(cwd);
118
+ const { createWebpackConfig } = await import("./create-webpack-config.js");
119
+ logger.info `Using ${evjsConfig ? "ev.config.ts" : "zero-config defaults"}`;
120
+ const webpackConfig = createWebpackConfig(evjsConfig, cwd);
121
+ return { evjsConfig, webpackConfig };
122
+ }
123
+ program
124
+ .command("dev")
125
+ .description("Start development server")
126
+ .action(async () => {
127
+ const cwd = process.cwd();
128
+ process.env.NODE_ENV ??= "development";
129
+ const { evjsConfig, webpackConfig } = await resolveWebpackConfig(cwd);
130
+ const serverPort = evjsConfig?.server?.dev?.port ?? CONFIG_DEFAULTS.serverPort;
131
+ logger.info `Starting development server...`;
132
+ try {
133
+ const webpack = esmRequire("webpack");
134
+ const WebpackDevServer = esmRequire("webpack-dev-server");
135
+ const compiler = webpack(webpackConfig);
136
+ const devServerOptions = webpackConfig.devServer ?? {};
137
+ const server = new WebpackDevServer(devServerOptions, compiler);
138
+ await server.start();
139
+ // Background: start Node API when server bundle is ready
140
+ let apiStarted = false;
141
+ compiler.hooks.done.tap("EvDevServer", async () => {
142
+ if (apiStarted)
143
+ return;
144
+ const manifestPath = path.resolve(cwd, "dist/manifest.json");
145
+ const bootstrapPath = path.resolve(cwd, "dist/server/_dev_start.cjs");
146
+ if (fs.existsSync(manifestPath)) {
147
+ apiStarted = true;
148
+ const backendConfig = evjsConfig?.server?.backend ?? "node";
149
+ const [backend, ...backendExtraArgs] = backendConfig.split(/\s+/);
150
+ logger.info `Server bundle detected, starting ${backend} API...`;
151
+ try {
152
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
153
+ const serverBundlePath = path.resolve(cwd, "dist/server", manifest.server.entry);
154
+ fs.writeFileSync(bootstrapPath, [
155
+ `const bundle = require(${JSON.stringify(serverBundlePath)});`,
156
+ `const app = bundle.createApp({ endpoint: ${JSON.stringify(evjsConfig?.server?.endpoint ?? CONFIG_DEFAULTS.endpoint)} });`,
157
+ `const { serve } = require("@evjs/runtime/server/node");`,
158
+ `serve(app, { port: ${serverPort} });`,
159
+ ].join("\n"));
160
+ // node gets --watch flags; other runtimes use their own args as-is
161
+ const backendArgs = backend === "node"
162
+ ? [
163
+ "--watch",
164
+ "--watch-preserve-output",
165
+ ...backendExtraArgs,
166
+ bootstrapPath,
167
+ ]
168
+ : [...backendExtraArgs, bootstrapPath];
169
+ // Don't await execa here since it's a long-running watch process
170
+ execa(backend, backendArgs, {
171
+ stdio: "inherit",
172
+ env: { ...process.env, NODE_ENV: "development" },
173
+ }).catch(() => {
174
+ apiStarted = false;
175
+ });
176
+ }
177
+ catch (err) {
178
+ logger.error `Server backend failed: ${err}`;
179
+ apiStarted = false;
180
+ }
181
+ }
182
+ });
183
+ }
184
+ catch (err) {
185
+ logger.error `Dev server failed to start: ${err}`;
186
+ process.exit(1);
187
+ }
188
+ });
189
+ program
190
+ .command("build")
191
+ .description("Build project for production")
192
+ .action(async () => {
193
+ const cwd = process.cwd();
194
+ process.env.NODE_ENV ??= "production";
195
+ const { webpackConfig } = await resolveWebpackConfig(cwd);
196
+ logger.info `Building for production...`;
197
+ const webpack = esmRequire("webpack");
198
+ const compiler = webpack(webpackConfig);
199
+ await new Promise((resolve, reject) => {
200
+ compiler.run((err, stats) => {
201
+ if (err) {
202
+ reject(err);
203
+ return;
204
+ }
205
+ console.log(stats.toString({
206
+ colors: true,
207
+ modules: false,
208
+ children: true,
209
+ }));
210
+ if (stats.hasErrors()) {
211
+ process.exit(1);
212
+ }
213
+ compiler.close(() => resolve());
214
+ });
215
+ });
216
+ logger.info `Build complete!`;
217
+ });
218
+ program.parse();
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const CONFIG_FILES = ["ev.config.ts", "ev.config.js", "ev.config.mjs"];
4
+ /**
5
+ * Ensure a TypeScript loader is registered before importing `.ts` config files.
6
+ * Tries `@swc-node/register/esm-register` (ships alongside `@swc/core` which
7
+ * the CLI already bundles), then falls back to Node's built-in `--loader tsx`
8
+ * pathway. If neither is available the raw `import()` is attempted anyway —
9
+ * Node will throw a clear error telling the user to install a loader.
10
+ */
11
+ async function ensureTsLoader() {
12
+ try {
13
+ // @ts-expect-error — optional dependency, may not be installed
14
+ await import("@swc-node/register/esm-register");
15
+ }
16
+ catch {
17
+ // Loader not available — Node may still handle .ts via --loader flag
18
+ }
19
+ }
20
+ /**
21
+ * Load evjs config from the project root.
22
+ *
23
+ * Looks for `ev.config.ts`, `.js`, or `.mjs` in the given directory.
24
+ * Returns undefined if no config file is found.
25
+ */
26
+ export async function loadConfig(cwd) {
27
+ for (const filename of CONFIG_FILES) {
28
+ const configPath = path.resolve(cwd, filename);
29
+ if (fs.existsSync(configPath)) {
30
+ // Register TS loader for .ts config files
31
+ if (filename.endsWith(".ts")) {
32
+ await ensureTsLoader();
33
+ }
34
+ const mod = await import(configPath);
35
+ return mod.default ?? mod;
36
+ }
37
+ }
38
+ return undefined;
39
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@evjs/cli",
3
+ "version": "0.0.0",
4
+ "description": "CLI and configuration layer for the evjs framework",
5
+ "type": "module",
6
+ "main": "./dist/config.js",
7
+ "types": "./dist/config.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/config.d.ts",
11
+ "import": "./dist/config.js"
12
+ }
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "bin": {
18
+ "ev": "dist/index.js"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "templates"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "dev": "tsc -w",
27
+ "test": "vitest run",
28
+ "check-types": "tsc --noEmit",
29
+ "prepare": "npm run build"
30
+ },
31
+ "dependencies": {
32
+ "@evjs/webpack-plugin": "*",
33
+ "@logtape/logtape": "^2.0.4",
34
+ "@swc/core": "^1.2.147",
35
+ "commander": "^12.1.0",
36
+ "execa": "^9.5.2",
37
+ "fs-extra": "^11.3.0",
38
+ "glob": "^13.0.6",
39
+ "html-webpack-plugin": "^5.6.6",
40
+ "picocolors": "^1.1.1",
41
+ "prompts": "^2.4.2",
42
+ "swc-loader": "^0.2.7",
43
+ "webpack": "^5.105.4",
44
+ "webpack-cli": "^6.0.1",
45
+ "webpack-dev-server": "^5.2.3"
46
+ },
47
+ "devDependencies": {
48
+ "@types/fs-extra": "^11.0.4",
49
+ "@types/node": "^22.13.5",
50
+ "@types/prompts": "^2.4.9",
51
+ "typescript": "^5.7.3"
52
+ },
53
+ "homepage": "https://github.com/evaijs/evjs#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/evaijs/evjs/issues"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/evaijs/evjs.git"
60
+ },
61
+ "keywords": [
62
+ "evjs",
63
+ "cli",
64
+ "react",
65
+ "server-functions",
66
+ "scaffolding",
67
+ "dev-server",
68
+ "typescript",
69
+ "full-stack",
70
+ "framework"
71
+ ],
72
+ "license": "MIT",
73
+ "author": "xusd320"
74
+ }
@@ -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>ev — Basic CSR Example</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "example-basic-csr",
3
+ "version": "0.0.0",
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
+ }
@@ -0,0 +1,22 @@
1
+ import { createApp } from "@evjs/runtime/client";
2
+ import { rootRoute } from "./pages/__root";
3
+ import { aboutRoute } from "./pages/about";
4
+ import { homeRoute } from "./pages/home";
5
+ import { postDetailRoute, postsIndexRoute, postsRoute } from "./pages/posts";
6
+
7
+ const routeTree = rootRoute.addChildren([
8
+ homeRoute,
9
+ aboutRoute,
10
+ postsRoute.addChildren([postsIndexRoute, postDetailRoute]),
11
+ ]);
12
+
13
+ const app = createApp({ routeTree });
14
+
15
+ // Register router type for full IDE type-safety on useParams, useSearch, Link, etc.
16
+ declare module "@tanstack/react-router" {
17
+ interface Register {
18
+ router: typeof app.router;
19
+ }
20
+ }
21
+
22
+ app.render("#app");
@@ -0,0 +1,28 @@
1
+ import { createRootRoute, Link, Outlet } from "@evjs/runtime/client";
2
+
3
+ function Root() {
4
+ return (
5
+ <div style={{ fontFamily: "system-ui, sans-serif", padding: "1rem" }}>
6
+ <nav
7
+ style={{
8
+ display: "flex",
9
+ gap: "1rem",
10
+ borderBottom: "1px solid #e5e7eb",
11
+ paddingBottom: "0.5rem",
12
+ marginBottom: "1rem",
13
+ }}
14
+ >
15
+ <Link to="/" style={{ fontWeight: "bold" }}>
16
+ Home
17
+ </Link>
18
+ <Link to="/about">About</Link>
19
+ <Link to="/posts">Posts</Link>
20
+ </nav>
21
+ <Outlet />
22
+ </div>
23
+ );
24
+ }
25
+
26
+ export const rootRoute = createRootRoute({
27
+ component: Root,
28
+ });
@@ -0,0 +1,19 @@
1
+ import { createRoute } from "@evjs/runtime/client";
2
+ import { rootRoute } from "./__root";
3
+
4
+ function About() {
5
+ return (
6
+ <div>
7
+ <h1>About</h1>
8
+ <p>
9
+ Code-based routing with TanStack Router via <code>createApp</code>.
10
+ </p>
11
+ </div>
12
+ );
13
+ }
14
+
15
+ export const aboutRoute = createRoute({
16
+ getParentRoute: () => rootRoute,
17
+ path: "/about",
18
+ component: About,
19
+ });