react-client 1.0.6 → 1.0.8

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 (47) hide show
  1. package/README.md +163 -84
  2. package/dist/cli/commands/build.js +30 -47
  3. package/dist/cli/commands/build.ssr.js +31 -54
  4. package/dist/cli/commands/dev.js +205 -6
  5. package/dist/cli/commands/init.js +80 -30
  6. package/dist/cli/commands/preview.js +44 -16
  7. package/dist/cli/index.js +104 -18
  8. package/dist/utils/loadConfig.js +111 -0
  9. package/package.json +68 -34
  10. package/templates/react/index.html +1 -0
  11. package/templates/react/package.json +14 -0
  12. package/templates/react/src/App.jsx +1 -0
  13. package/templates/react/src/main.jsx +1 -0
  14. package/templates/react-ssr/index.html +1 -0
  15. package/templates/react-ssr/package.json +14 -0
  16. package/templates/react-ssr/src/entry-client.jsx +1 -0
  17. package/templates/react-ssr/src/entry-server.jsx +1 -0
  18. package/templates/react-ssr/src/pages/index.jsx +1 -0
  19. package/templates/react-ssr-ts/index.html +1 -0
  20. package/templates/react-ssr-ts/package.json +21 -0
  21. package/templates/react-ssr-ts/src/entry-client.js +5 -0
  22. package/templates/react-ssr-ts/src/entry-client.tsx +6 -0
  23. package/templates/react-ssr-ts/src/entry-server.js +4 -0
  24. package/templates/react-ssr-ts/src/entry-server.tsx +5 -0
  25. package/templates/react-ssr-ts/src/pages/index.js +2 -0
  26. package/templates/react-ssr-ts/src/pages/index.tsx +3 -0
  27. package/templates/react-ssr-ts/tsconfig.json +15 -0
  28. package/templates/react-tailwind/index.html +1 -0
  29. package/templates/react-tailwind/package.json +20 -0
  30. package/templates/react-tailwind/postcss.config.cjs +1 -0
  31. package/templates/react-tailwind/src/App.jsx +1 -0
  32. package/templates/react-tailwind/src/main.jsx +1 -0
  33. package/templates/react-tailwind/tailwind.config.cjs +1 -0
  34. package/templates/react-tailwind-ts/index.html +1 -0
  35. package/templates/react-tailwind-ts/package.json +24 -0
  36. package/templates/react-tailwind-ts/postcss.config.cjs +1 -0
  37. package/templates/react-tailwind-ts/src/App.js +2 -0
  38. package/templates/react-tailwind-ts/src/App.tsx +2 -0
  39. package/templates/react-tailwind-ts/src/index.css +1 -0
  40. package/templates/react-tailwind-ts/src/main.js +7 -0
  41. package/templates/react-tailwind-ts/src/main.tsx +8 -0
  42. package/templates/react-tailwind-ts/tailwind.config.cjs +1 -0
  43. package/templates/react-tailwind-ts/tsconfig.json +14 -0
  44. package/templates/react-ts/index.html +1 -0
  45. package/templates/react-ts/package.json +20 -0
  46. package/templates/react-ts/src/App.tsx +1 -0
  47. package/templates/react-ts/src/main.tsx +1 -0
package/README.md CHANGED
@@ -2,140 +2,219 @@
2
2
  [![npm](https://img.shields.io/npm/dt/react-client.svg)](https://npm-stat.com/charts.html?package=react-client)
3
3
  [![GitHub issues](https://img.shields.io/github/issues/venkateshsundaram/react-client.svg)](https://github.com/venkateshsundaram/react-client/issues)
4
4
 
5
- react-client is a lightweight CLI and runtime for building React apps with fast iteration. It is designed to be esbuild-based, Node-native, and modular like Vite/Next.js while remaining minimal.
5
+ **react-client** is a next-generation CLI and runtime for building React apps with instant feedback, fast iteration, and a beautiful developer experience.
6
6
 
7
- ## Table of Contents
7
+ Built for simplicity, designed for speed ⚔
8
+
9
+ ---
10
+
11
+ ## šŸš€ Table of Contents
8
12
  - [Installation](#installation)
9
13
  - [With Config](#with-config)
10
- - [Wiki](#wiki)
11
- - [Available templates](#available-templates)
12
- - [Features supported by the CLI](#features-supported-by-the-cli)
13
- - [Template specifics](#template-specifics)
14
- * [react-ssr-ts](#react-ssr-ts)
15
- * [react-tailwind-ts](#react-tailwind-ts)
16
- - [How the CLI wires features](#how-the-cli-wires-features)
17
- - [Local testing checklist](#local-testing-checklist)
14
+ - [Available Templates](#available-templates)
15
+ - [Core Features](#core-features)
16
+ - [How It Works](#how-it-works)
17
+ - [Local Development](#local-development)
18
18
  - [Troubleshooting](#troubleshooting)
19
- - [Extending & Contributing](#extending-contributing)
20
19
  - [Contributing](#contributing)
21
20
  - [Publishing](#publishing)
22
- - [Feedbacks and Issues](#feedbacks-and-issues)
21
+ - [Feedback](#feedback)
23
22
  - [License](#license)
24
23
 
25
- ## Installation
24
+ ---
26
25
 
27
- The React-client package lives in npm.
26
+ ## 🧩 Installation
28
27
 
29
- To install the latest stable version, run the following command:
28
+ Install globally and scaffold your first app:
30
29
 
31
30
  ```bash
32
31
  npm install -g react-client
33
32
  react-client init myapp --template react-ts
34
33
  cd myapp
34
+ npm install
35
35
  npm run dev
36
36
  ```
37
37
 
38
- ## With Config
38
+ This launches the **custom dev server** — not Vite — built on **Connect + WebSocket + esbuild**, featuring:
39
+ - Instant rebuilds
40
+ - React Fast Refresh (HMR)
41
+ - Auto port detection & confirmation prompt
42
+ - In-browser overlay with syntax-highlighted code frames
39
43
 
40
- The `init` command supports a small set of helpers for scaffolding projects from the bundled templates. Notably:
44
+ ---
41
45
 
42
- - `--template <name>`: choose a template (defaults to `react-ts`).
43
- - `--with-config`: also create a `react-client.config.ts` file in the generated project with a minimal `defineConfig({})` stub.
46
+ ## āš™ļø With Config
44
47
 
45
- Example:
48
+ You can generate a project-level configuration file using `--with-config`.
46
49
 
47
50
  ```bash
48
51
  react-client init myapp --template react-ts --with-config
49
- # creates `myapp/` and writes `myapp/react-client.config.ts`
50
52
  ```
51
53
 
52
- ## Wiki
54
+ Creates:
55
+
56
+ ```js
57
+ // react-client.config.js
58
+ import { defineConfig } from 'react-client/config';
59
+
60
+ export default defineConfig({
61
+ root: './src',
62
+ server: { port: 5173 },
63
+ build: { outDir: '.react-client/build' }
64
+ });
65
+ ```
66
+
67
+ āœ… Loaded automatically by the CLI
68
+ āœ… Type-safe with IntelliSense via `defineConfig()`
69
+ āœ… Supports `.js`, `.mjs`, `.ts` (auto-compiled)
70
+
71
+ ---
72
+
73
+ ## 🧰 Available Templates
74
+
75
+ | Template | Description |
76
+ |-----------|-------------|
77
+ | `react` | JavaScript SPA |
78
+ | `react-ts` | TypeScript SPA |
79
+ | `react-ssr` | JavaScript SSR |
80
+ | `react-ssr-ts` | TypeScript SSR |
81
+ | `react-tailwind` | JS + Tailwind |
82
+ | `react-tailwind-ts` | TS + Tailwind |
83
+
84
+ Each template is pre-configured for esbuild, HMR, and fast bootstrapping.
85
+
86
+ ---
87
+
88
+ ## šŸ’Ž Core Features
89
+
90
+ - ⚔ **Custom Dev Server (no Vite)** — Connect + WebSocket + esbuild
91
+ - šŸ” **React Fast Refresh (HMR)** — State-preserving reloads
92
+ - šŸ’„ **Vite-style Overlay** — Syntax-highlighted stack frames, clickable file links (`vscode://file`)
93
+ - šŸ” **Source Map Stack Mapping** — Maps runtime errors to original TS/JS source lines
94
+ - šŸ’¬ **Auto Port Detection** — Prompts when default port 5173 is occupied
95
+ - 🧠 **Smart Config Loader** — Detects project root, compiles `.ts` configs dynamically
96
+ - šŸŽØ **PrismJS Highlighting** — For pretty overlay code frames
97
+ - 🧱 **SSR Runtime Support** — For server-side templates (`react-ssr*`)
98
+ - 🧩 **Generators** — Create components, routes, and tests instantly
99
+ - šŸ”Œ **Plugin Hook System** — Extendable with `configResolved`, `transform`, `buildEnd`
100
+
101
+ ---
102
+
103
+ ## 🧬 How It Works
104
+
105
+ **Under the hood:**
106
+
107
+ 1. **esbuild** handles bundling, incremental rebuilds, and sourcemaps.
108
+ 2. **Connect** serves files and APIs (React Refresh runtime, overlay, source-map).
109
+ 3. **WebSocket** pushes HMR updates and overlay messages.
110
+ 4. **Chokidar** watches `/src` for changes and triggers rebuilds.
111
+ 5. **Overlay UI** (via PrismJS) displays mapped stack frames with syntax highlighting.
112
+
113
+ ---
53
114
 
54
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/venkateshsundaram/react-client)
115
+ ## 🧪 Local Development
55
116
 
56
- ## Available templates
117
+ To test `react-client` locally:
57
118
 
58
- - react — JavaScript SPA
59
- - react-ts — TypeScript SPA
60
- - react-ssr — JavaScript SSR app
61
- - react-ssr-ts — TypeScript SSR app
62
- - react-tailwind — JavaScript + Tailwind
63
- - react-tailwind-ts — TypeScript + Tailwind
119
+ ```bash
120
+ cd ~/Desktop/Workspace/Hoppy-projects/react-client
121
+ npm run build
122
+ npm link
123
+ cd myapp
124
+ react-client dev
125
+ ```
64
126
 
65
- ## Features supported by the CLI
127
+ If you run it from inside the CLI repo, it auto-detects and switches to `myapp/` as the root.
66
128
 
67
- - React Fast Refresh: HMR that preserves component state
68
- - PostCSS/Tailwind auto-injection and setup for Tailwind templates
69
- - CSS Modules support with hashing and AST-based import rewriting
70
- - Asset hashing and manifest generation for SSR runtime lookups
71
- - Plugin hook system (configResolved, transform, buildEnd)
72
- - Generators: component/route/test scaffolding
73
- - SSR runtime with server that consults manifest.json
129
+ ---
74
130
 
75
- ## Template specifics
131
+ ## 🧩 Troubleshooting
76
132
 
77
- ### react-ssr-ts
133
+ ### āŒ Config not loading
134
+ Make sure `react-client.config.js` exists in your project root (not `.ts`).
135
+
136
+ ```bash
137
+ /Users/<you>/myapp/react-client.config.js
138
+ ```
139
+
140
+ ### āŒ `react-refresh/runtime` not found
141
+ Install in the CLI or the project:
142
+ ```bash
143
+ npm install react-refresh
144
+ ```
78
145
 
79
- - `src/entry-client.tsx` — client hydration entry
80
- - `src/entry-server.tsx` — server rendering entry (exports `render(url)`)
81
- - `src/pages/` — route components
82
- - `index.html` — template used by build system
83
- - `package.json` lists devDependencies required for local testing (esbuild, react-refresh)
146
+ ### āš ļø Port already in use
147
+ CLI will auto-detect and prompt:
148
+ ```
149
+ Port 5173 is occupied. Use 5174 instead? (Y/n)
150
+ ```
84
151
 
85
- ### react-tailwind-ts
152
+ ### āš ļø Permission denied
153
+ Ensure your CLI entry file is executable:
154
+ ```bash
155
+ chmod +x dist/cli/index.js
156
+ npm link
157
+ ```
86
158
 
87
- - `postcss.config.cjs` and `tailwind.config.cjs` included
88
- - `src/index.css` with Tailwind directives
89
- - `package.json` includes tailwind/postcss devDependencies (install locally in the app)
159
+ ---
90
160
 
91
- ## How the CLI wires features
161
+ ## šŸ§‘ā€šŸ’» Contributing
92
162
 
93
- 1. **HMR & React Refresh**: dev plugin injects `import '/@react-refresh-shim'` into files that import React. During build, the CLI attempts to `require.resolve('react-refresh/runtime')` and copy it into the client vendor folder to be included in bundles.
94
- 2. **CSS Modules**: build step uses `postcss-modules` to produce JSON class maps and then uses `es-module-lexer` + `magic-string` to rewrite `.module.css` import specifiers to the hashed filenames emitted by the bundler.
95
- 3. **Manifest**: after building, the CLI hashes outputs and writes `client/manifest.json` mapping original -> hashed filenames. The SSR server uses this manifest to serve correct hashed assets and update HTML script tags dynamically.
96
- 4. **PostCSS/Tailwind**: if `postcss.config.cjs` or `tailwind.config.cjs` exists in the project, the build process runs `npx postcss` to process CSS and includes the output in the final client bundle.
163
+ We welcome contributions!
164
+ Read the [Contributing Guide](./CONTRIBUTING.md) for setup instructions.
97
165
 
98
- ## Local testing checklist
166
+ ```bash
167
+ npm run lint
168
+ npm run test
169
+ npm run build
170
+ ```
99
171
 
100
- 1. Node 20+ installed.
101
- 2. Scaffold a new app using the CLI `init` command pointing to these templates.
102
- 3. `npm install` in the new app to get dependencies.
103
- 4. Run `node /path/to/react-client/dist/cli/index.js dev` for HMR-enabled dev server, or `build:ssr` to produce SSR build and run `node dist/server/server.js` to preview.
172
+ ---
104
173
 
105
- ## Troubleshooting
174
+ ## šŸš€ Publishing
106
175
 
107
- - If `react-refresh/runtime` isn't found during build, install in the environment running the CLI:
108
- ```bash
109
- npm install --save-dev react-refresh
110
- ```
111
- - For PostCSS/Tailwind errors, ensure tailwind & postcss installed in the target app:
112
- ```bash
113
- npm install --save-dev tailwindcss postcss autoprefixer
114
- ```
115
- - For AST rewriting, ensure `es-module-lexer` and `magic-string` are installed where the CLI runs:
116
- ```bash
117
- npm install --save-dev es-module-lexer magic-string
118
- ```
176
+ Before publishing:
177
+ 1. Update version in `package.json`
178
+ 2. Run a full build
179
+ 3. Ensure the entry file has execute permission
119
180
 
120
- ## Extending & Contributing
181
+ ```bash
182
+ npm run build
183
+ npm publish
184
+ ```
121
185
 
122
- - Add plugins under `src/plugins` with `configResolved` and `transform` hooks.
123
- - Tests: add Jest configurations inside templates and include `test` npm scripts.
186
+ Your package now includes:
187
+ - `#!/usr/bin/env node` shebang
188
+ - Auto-detecting config loader
189
+ - Built-in React Refresh runtime
124
190
 
125
- ## Contributing
191
+ ---
126
192
 
127
- Development of react-client happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving react-client.
193
+ ## šŸ’¬ Feedback
128
194
 
129
- - [Contributing Guide](./CONTRIBUTING.md)
195
+ Found an issue or have a feature request?
196
+ šŸ‘‰ [Open an issue](https://github.com/venkateshsundaram/react-client/issues)
130
197
 
131
- ## Publishing
198
+ ---
132
199
 
133
- Before pushing your changes to Github, make sure that `version` in `package.json` is changed to newest version. Then run `npm install` for synchronize it to `package-lock.json` and `pnpm install` for synchronize it to `pnpm-lock.yaml`
200
+ ## 🪪 License
134
201
 
135
- ## Feedbacks and Issues
202
+ **MIT Licensed** Ā© [Venkatesh Sundaram](https://github.com/venkateshsundaram)
136
203
 
137
- Feel free to open issues if you found any feedback or issues on `react-client`. And feel free if you want to contribute too! šŸ˜„
204
+ ---
138
205
 
139
- ## License
206
+ ## šŸ—ŗļø Architecture Overview (Bonus)
140
207
 
141
- React-client is [MIT licensed](./LICENSE).
208
+ ```text
209
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
210
+ │ react-client (CLI) │
211
+ │ ā”œā”€ā”€ esbuild (watch/bundle)
212
+ │ ā”œā”€ā”€ connect (dev server)
213
+ │ ā”œā”€ā”€ websocket (HMR)
214
+ │ ā”œā”€ā”€ prismjs overlay
215
+ │ └── chokidar (file watch)
216
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
217
+ │
218
+ ā–¼
219
+ Browser ↔ Live HMR + Overlay
220
+ ```
@@ -7,55 +7,38 @@ exports.default = build;
7
7
  const esbuild_1 = __importDefault(require("esbuild"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
- const child_process_1 = require("child_process");
11
- const crypto_1 = __importDefault(require("crypto"));
12
- function hash(buf) {
13
- return crypto_1.default.createHash('sha256').update(buf).digest('hex').slice(0, 8);
14
- }
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const loadConfig_1 = require("../../utils/loadConfig");
15
12
  async function build() {
16
13
  const root = process.cwd();
17
- const entry = path_1.default.join(root, 'src', 'main.tsx');
18
- const out = path_1.default.join(root, 'dist');
19
- await fs_extra_1.default.remove(out);
20
- const result = await esbuild_1.default.build({
21
- entryPoints: [entry],
22
- bundle: true,
23
- outdir: path_1.default.join(out, 'client'),
24
- metafile: true,
25
- minify: true,
26
- loader: {
27
- '.ts': 'ts',
28
- '.tsx': 'tsx',
29
- '.js': 'jsx',
30
- '.jsx': 'jsx',
31
- '.png': 'file',
32
- '.jpg': 'file',
33
- '.svg': 'file',
34
- },
35
- });
36
- const postcssConfig = path_1.default.join(root, 'postcss.config.cjs');
37
- if (fs_extra_1.default.existsSync(postcssConfig)) {
38
- try {
39
- (0, child_process_1.execSync)('npx postcss ' + path_1.default.join(out, 'client', '*.css') + ' -d ' + path_1.default.join(out, 'client'));
40
- }
41
- catch (e) {
42
- console.warn('PostCSS failed', e && e.message);
43
- }
14
+ const config = await (0, loadConfig_1.loadReactClientConfig)(root);
15
+ const appRoot = path_1.default.resolve(root, config.root || '.');
16
+ const outDir = path_1.default.join(appRoot, config.build?.outDir || '.react-client/build');
17
+ console.log(chalk_1.default.cyan(`\nšŸ—ļø Building project...`));
18
+ console.log(chalk_1.default.gray(`Root: ${appRoot}`));
19
+ console.log(chalk_1.default.gray(`Output: ${outDir}\n`));
20
+ const entry = path_1.default.join(appRoot, 'src', 'main.tsx');
21
+ if (!fs_extra_1.default.existsSync(entry)) {
22
+ console.error(chalk_1.default.red('āŒ Entry not found: src/main.tsx'));
23
+ process.exit(1);
24
+ }
25
+ await fs_extra_1.default.ensureDir(outDir);
26
+ try {
27
+ await esbuild_1.default.build({
28
+ entryPoints: [entry],
29
+ bundle: true,
30
+ minify: true,
31
+ sourcemap: true,
32
+ outdir: outDir,
33
+ define: { 'process.env.NODE_ENV': '"production"' },
34
+ loader: { '.ts': 'ts', '.tsx': 'tsx', '.js': 'jsx', '.jsx': 'jsx' },
35
+ });
36
+ console.log(chalk_1.default.green(`āœ… Build completed successfully!`));
37
+ console.log(chalk_1.default.gray(`Output directory: ${outDir}`));
44
38
  }
45
- const manifest = {};
46
- for (const outPath in result.metafile.outputs) {
47
- const abs = path_1.default.join(root, outPath);
48
- if (!fs_extra_1.default.existsSync(abs))
49
- continue;
50
- const data = await fs_extra_1.default.readFile(abs);
51
- const h = hash(data);
52
- const rel = outPath.replace(/^client\//, '');
53
- const ext = path_1.default.extname(rel);
54
- const hashed = rel.replace(ext, '.' + h + ext);
55
- await fs_extra_1.default.ensureDir(path_1.default.join(out, 'client', path_1.default.dirname(hashed)));
56
- await fs_extra_1.default.move(abs, path_1.default.join(out, 'client', hashed));
57
- manifest[rel] = hashed;
39
+ catch (err) {
40
+ const msg = err instanceof Error ? err.message : String(err);
41
+ console.error('āŒ Build failed:', msg);
42
+ process.exit(1);
58
43
  }
59
- await fs_extra_1.default.writeFile(path_1.default.join(out, 'client', 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
60
- console.log('Built client to', path_1.default.join(out, 'client'));
61
44
  }
@@ -7,63 +7,40 @@ exports.default = buildSsr;
7
7
  const esbuild_1 = __importDefault(require("esbuild"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
- const crypto_1 = __importDefault(require("crypto"));
11
- function hash(buf) {
12
- return crypto_1.default.createHash('sha256').update(buf).digest('hex').slice(0, 8);
13
- }
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const loadConfig_1 = require("../../utils/loadConfig");
14
12
  async function buildSsr() {
15
13
  const root = process.cwd();
16
- const clientEntry = path_1.default.join(root, 'src', 'entry-client.tsx');
17
- const serverEntry = path_1.default.join(root, 'src', 'entry-server.tsx');
18
- if (!fs_extra_1.default.existsSync(clientEntry) || !fs_extra_1.default.existsSync(serverEntry)) {
19
- console.error('SSR entries missing');
14
+ const config = await (0, loadConfig_1.loadReactClientConfig)(root);
15
+ const appRoot = path_1.default.resolve(root, config.root || '.');
16
+ const outDir = path_1.default.join(appRoot, config.build?.outDir || '.react-client/ssr');
17
+ console.log(chalk_1.default.cyan(`\n🧱 Building SSR bundle...`));
18
+ console.log(chalk_1.default.gray(`Root: ${appRoot}`));
19
+ console.log(chalk_1.default.gray(`Output: ${outDir}\n`));
20
+ const entry = path_1.default.join(appRoot, 'src', 'server.tsx');
21
+ if (!fs_extra_1.default.existsSync(entry)) {
22
+ console.error(chalk_1.default.red('āŒ SSR entry not found: src/server.tsx'));
20
23
  process.exit(1);
21
24
  }
22
- const out = path_1.default.join(root, 'dist');
23
- await fs_extra_1.default.remove(out);
24
- const clientResult = await esbuild_1.default.build({
25
- entryPoints: [clientEntry],
26
- bundle: true,
27
- outdir: path_1.default.join(out, 'client'),
28
- metafile: true,
29
- minify: true,
30
- loader: {
31
- '.ts': 'ts',
32
- '.tsx': 'tsx',
33
- '.js': 'jsx',
34
- '.jsx': 'jsx',
35
- '.png': 'file',
36
- '.jpg': 'file',
37
- '.svg': 'file',
38
- },
39
- });
40
- const _serverResult = await esbuild_1.default.build({
41
- entryPoints: [serverEntry],
42
- bundle: true,
43
- platform: 'node',
44
- format: 'cjs',
45
- outfile: path_1.default.join(out, 'server', 'entry-server.js'),
46
- external: ['react', 'react-dom'],
47
- });
48
- const manifest = {};
49
- for (const outPath in clientResult.metafile.outputs) {
50
- if (!outPath.startsWith('client/'))
51
- continue;
52
- const abs = path_1.default.join(out, outPath);
53
- if (!fs_extra_1.default.existsSync(abs))
54
- continue;
55
- const data = await fs_extra_1.default.readFile(abs);
56
- const h = hash(data);
57
- const rel = outPath.replace(/^client\//, '');
58
- const ext = path_1.default.extname(rel);
59
- const hashed = rel.replace(ext, '.' + h + ext);
60
- await fs_extra_1.default.ensureDir(path_1.default.join(out, 'client', path_1.default.dirname(hashed)));
61
- await fs_extra_1.default.move(abs, path_1.default.join(out, 'client', hashed));
62
- manifest[rel] = hashed;
25
+ await fs_extra_1.default.ensureDir(outDir);
26
+ try {
27
+ await esbuild_1.default.build({
28
+ entryPoints: [entry],
29
+ bundle: true,
30
+ platform: 'node',
31
+ format: 'esm',
32
+ target: 'node18',
33
+ external: ['react', 'react-dom'],
34
+ outdir: outDir,
35
+ define: { 'process.env.NODE_ENV': '"production"' },
36
+ loader: { '.ts': 'ts', '.tsx': 'tsx' },
37
+ });
38
+ console.log(chalk_1.default.green(`āœ… SSR build completed successfully!`));
39
+ console.log(chalk_1.default.gray(`Output directory: ${outDir}`));
40
+ }
41
+ catch (err) {
42
+ const msg = err instanceof Error ? err.message : String(err);
43
+ console.error('āŒ SSR build failed:', msg);
44
+ process.exit(1);
63
45
  }
64
- await fs_extra_1.default.writeFile(path_1.default.join(out, 'client', 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
65
- const runtime = `const http = require('http');const fs = require('fs');const path=require('path');const { render } = require('./entry-server.js');const clientDir = path.join(__dirname,'..','client');const manifest = JSON.parse(fs.readFileSync(path.join(clientDir,'manifest.json'),'utf8'));const PORT = process.env.PORT||3000;const server = http.createServer(async (req,res)=>{ try{ if(req.url && req.url.startsWith('/client/')){ const rel = req.url.replace('/client/',''); const mapped = manifest[rel] || rel; const p = path.join(clientDir, mapped); if(fs.existsSync(p)) { res.writeHead(200); res.end(fs.readFileSync(p)); return; } } const htmlPath = path.join(clientDir,'index.html'); let html = '<!doctype html><html><head></head><body><div id="root"></div><script type="module" src="/client/bundle.js"></script></body></html>'; if(fs.existsSync(htmlPath)) html = fs.readFileSync(htmlPath,'utf8'); const content = await render(req.url||'/'); const outHtml = html.replace('<div id="root"></div>','<div id="root">'+content+'</div>'); res.writeHead(200,{'Content-Type':'text/html'}); res.end(outHtml);}catch(e){ console.error(e); res.writeHead(500); res.end('SSR error');}});server.listen(PORT,()=>console.log('SSR server running on port '+PORT));`;
66
- await fs_extra_1.default.ensureDir(path_1.default.join(out, 'server'));
67
- await fs_extra_1.default.writeFile(path_1.default.join(out, 'server', 'server.js'), runtime, 'utf8');
68
- console.log('SSR build complete at', out);
69
46
  }