react-client 1.0.7 → 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.
package/README.md CHANGED
@@ -2,31 +2,30 @@
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
@@ -36,229 +35,186 @@ npm install
36
35
  npm run dev
37
36
  ```
38
37
 
39
- ## 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
40
43
 
41
- The `init` command supports a small set of helpers for scaffolding projects from the bundled templates. Notably:
44
+ ---
42
45
 
43
- - `--template <name>`: choose a template (defaults to `react-ts`).
44
- - `--with-config`: also create a `react-client.config.ts` file in the generated project with a minimal `defineConfig({})` stub.
46
+ ## āš™ļø With Config
45
47
 
46
- Example:
48
+ You can generate a project-level configuration file using `--with-config`.
47
49
 
48
50
  ```bash
49
51
  react-client init myapp --template react-ts --with-config
50
- # creates `myapp/` and writes `myapp/react-client.config.ts`
51
52
  ```
52
53
 
53
- ## Wiki
54
+ Creates:
54
55
 
55
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/venkateshsundaram/react-client)
56
+ ```js
57
+ // react-client.config.js
58
+ import { defineConfig } from 'react-client/config';
56
59
 
57
- ## Available templates
60
+ export default defineConfig({
61
+ root: './src',
62
+ server: { port: 5173 },
63
+ build: { outDir: '.react-client/build' }
64
+ });
65
+ ```
58
66
 
59
- - react — JavaScript SPA
60
- - react-ts — TypeScript SPA
61
- - react-ssr — JavaScript SSR app
62
- - react-ssr-ts — TypeScript SSR app
63
- - react-tailwind — JavaScript + Tailwind
64
- - react-tailwind-ts — TypeScript + Tailwind
67
+ āœ… Loaded automatically by the CLI
68
+ āœ… Type-safe with IntelliSense via `defineConfig()`
69
+ āœ… Supports `.js`, `.mjs`, `.ts` (auto-compiled)
65
70
 
66
- ## Features supported by the CLI
71
+ ---
67
72
 
68
- - React Fast Refresh: HMR that preserves component state
69
- - PostCSS/Tailwind auto-injection and setup for Tailwind templates
70
- - CSS Modules support with hashing and AST-based import rewriting
71
- - Asset hashing and manifest generation for SSR runtime lookups
72
- - Plugin hook system (configResolved, transform, buildEnd)
73
- - Generators: component/route/test scaffolding
74
- - SSR runtime with server that consults manifest.json
73
+ ## 🧰 Available Templates
75
74
 
76
- ## Template specifics
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 |
77
83
 
78
- ### react
84
+ Each template is pre-configured for esbuild, HMR, and fast bootstrapping.
79
85
 
80
- - `src/App.jsx` — main app component
81
- - `src/main.jsx` — client entry that mounts React to the DOM
82
- - `index.html` — HTML template used by the bundler
83
- - `package.json` lists `react` and `react-dom` as dependencies and includes devDependencies (esbuild, scripts) for local testing
86
+ ---
84
87
 
85
- - `--with-config`: creates `react-client.config.ts` with a minimal `defineConfig({})` stub and helpful comments (dev server port, root) tailored to a client-only app.
88
+ ## šŸ’Ž Core Features
86
89
 
87
- ```ts
88
- // react / client-only example
89
- import { defineConfig } from 'react-client'
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`
90
100
 
91
- export default defineConfig({
92
- root: '.',
93
- server: { port: 3000 },
94
- build: { outDir: 'dist/client' }
95
- })
96
- ```
97
-
98
- ### react-ts
101
+ ---
99
102
 
100
- - `src/App.tsx` — main app component (TypeScript + JSX)
101
- - `src/main.tsx` — client entry (TypeScript) that mounts the app
102
- - `tsconfig.json` — minimal TypeScript configuration tailored for the template
103
- - `index.html` — HTML template used by the bundler
104
- - `package.json` includes `react`, `react-dom` and `devDependencies` like `typescript` and `@types/react`, `@types/react-dom` for local development
103
+ ## 🧬 How It Works
105
104
 
106
- - `--with-config`: creates `react-client.config.ts` with a TypeScript-friendly `defineConfig({})` stub (typed imports) and suggested TypeScript-related defaults (e.g., `root`, `esbuild` TS options) comments.
105
+ **Under the hood:**
107
106
 
108
- ```ts
109
- // react-ts example (TypeScript-friendly)
110
- import { defineConfig } from 'react-client'
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.
111
112
 
112
- export default defineConfig({
113
- root: '.',
114
- server: { port: 3000 },
115
- build: { outDir: 'dist/client' },
116
- // TS-specific hints: configure `tsconfig` or esbuild TS options if needed
117
- })
118
- ```
113
+ ---
119
114
 
120
- ### react-ssr
115
+ ## 🧪 Local Development
121
116
 
122
- - `src/entry-client.jsx` — client hydration entry
123
- - `src/entry-server.jsx` — server rendering entry (exports `render(url)` or similar API)
124
- - `src/pages/` — route components used by the SSR renderer
125
- - `index.html` — base HTML template used by the build system
126
- - `package.json` lists devDependencies required for local testing (esbuild, react-refresh) and provides scripts for building and running the SSR preview
117
+ To test `react-client` locally:
127
118
 
128
- - `--with-config`: the generated `react-client.config.ts` includes an SSR-focused stub with comments for server entry/port and output directories (hints for `ssr` or `server` options) so you can quickly enable SSR-related plugins.
129
-
130
- ```ts
131
- // react-ssr example
132
- import { defineConfig } from 'react-client'
133
-
134
- export default defineConfig({
135
- root: '.',
136
- server: { port: 3000 },
137
- ssr: {
138
- entry: 'src/entry-server.jsx', // change to .tsx for TS template
139
- outDir: 'dist/server'
140
- }
141
- })
119
+ ```bash
120
+ cd ~/Desktop/Workspace/Hoppy-projects/react-client
121
+ npm run build
122
+ npm link
123
+ cd myapp
124
+ react-client dev
142
125
  ```
143
126
 
144
- ### react-ssr-ts
127
+ If you run it from inside the CLI repo, it auto-detects and switches to `myapp/` as the root.
145
128
 
146
- - `src/entry-client.tsx` — client hydration entry
147
- - `src/entry-server.tsx` — server rendering entry (exports `render(url)`)
148
- - `src/pages/` — route components
149
- - `index.html` — template used by build system
150
- - `package.json` lists devDependencies required for local testing (esbuild, react-refresh)
151
- - `tsconfig.json` and `@types/*` entries are included to support TypeScript development
129
+ ---
152
130
 
153
- - `--with-config`: creates a typed `react-client.config.ts` stub that includes notes for SSR/TypeScript integration (server entry, `tsconfig` path, and build output locations) to help you customize server-side rendering options.
131
+ ## 🧩 Troubleshooting
154
132
 
155
- ```ts
156
- // react-ssr-ts example (typed)
157
- import { defineConfig } from 'react-client'
133
+ ### āŒ Config not loading
134
+ Make sure `react-client.config.js` exists in your project root (not `.ts`).
158
135
 
159
- export default defineConfig({
160
- root: '.',
161
- server: { port: 3000 },
162
- ssr: {
163
- entry: 'src/entry-server.tsx',
164
- outDir: 'dist/server'
165
- },
166
- // Tip: point to `tsconfig.json` or adjust esbuild TS options if you customize builds
167
- })
136
+ ```bash
137
+ /Users/<you>/myapp/react-client.config.js
168
138
  ```
169
139
 
170
- ### react-tailwind
171
-
172
- - `postcss.config.cjs` and `tailwind.config.cjs` included
173
- - `src/index.css` with Tailwind directives (`@tailwind base; @tailwind components; @tailwind utilities;`)
174
- - `index.html` and JS entries (`src/main.jsx`, `src/App.jsx`) wired to import the generated CSS
175
- - `package.json` includes Tailwind/PostCSS devDependencies (install locally in the app)
176
-
177
- - `--with-config`: creates `react-client.config.ts` containing a minimal stub plus a Tailwind hint block that shows how to enable/inject PostCSS/Tailwind processing (for example enabling the PostCSS plugin or pointing to `postcss.config.cjs`).
178
-
179
- ```ts
180
- // react-tailwind example
181
- import { defineConfig } from 'react-client'
140
+ ### āŒ `react-refresh/runtime` not found
141
+ Install in the CLI or the project:
142
+ ```bash
143
+ npm install react-refresh
144
+ ```
182
145
 
183
- export default defineConfig({
184
- root: '.',
185
- server: { port: 3000 },
186
- css: {
187
- postcssConfig: 'postcss.config.cjs'
188
- }
189
- })
146
+ ### āš ļø Port already in use
147
+ CLI will auto-detect and prompt:
148
+ ```
149
+ Port 5173 is occupied. Use 5174 instead? (Y/n)
190
150
  ```
191
151
 
192
- ### 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
+ ```
193
158
 
194
- - `postcss.config.cjs` and `tailwind.config.cjs` included
195
- - `src/index.css` with Tailwind directives
196
- - `src/App.tsx` / `src/main.tsx` — TypeScript entry points that import the CSS
197
- - `tsconfig.json` provided for TypeScript templates
198
- - `package.json` includes Tailwind/PostCSS devDependencies (install locally in the app)
159
+ ---
199
160
 
200
- - `--with-config`: generates a TypeScript `react-client.config.ts` stub that includes comments about wiring PostCSS/Tailwind and TypeScript settings (where to find `postcss.config.cjs` and `tsconfig.json`) to get Tailwind configured quickly.
161
+ ## šŸ§‘ā€šŸ’» Contributing
201
162
 
202
- ```ts
203
- // react-tailwind-ts example
204
- import { defineConfig } from 'react-client'
163
+ We welcome contributions!
164
+ Read the [Contributing Guide](./CONTRIBUTING.md) for setup instructions.
205
165
 
206
- export default defineConfig({
207
- root: '.',
208
- server: { port: 3000 },
209
- css: { postcssConfig: 'postcss.config.cjs' },
210
- // TypeScript hint: adjust tsconfig or esbuild options as needed
211
- })
166
+ ```bash
167
+ npm run lint
168
+ npm run test
169
+ npm run build
212
170
  ```
213
171
 
214
- ## How the CLI wires features
215
-
216
- 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.
217
- 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.
218
- 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.
219
- 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.
220
-
221
- ## Local testing checklist
172
+ ---
222
173
 
223
- 1. Node 20+ installed.
224
- 2. Scaffold a new app using the CLI `init` command pointing to these templates.
225
- 3. `npm install` in the new app to get dependencies.
226
- 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.
174
+ ## šŸš€ Publishing
227
175
 
228
- ## Troubleshooting
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
229
180
 
230
- - If `react-refresh/runtime` isn't found during build, install in the environment running the CLI:
231
- ```bash
232
- npm install --save-dev react-refresh
233
- ```
234
- - For PostCSS/Tailwind errors, ensure tailwind & postcss installed in the target app:
235
- ```bash
236
- npm install --save-dev tailwindcss postcss autoprefixer
237
- ```
238
- - For AST rewriting, ensure `es-module-lexer` and `magic-string` are installed where the CLI runs:
239
- ```bash
240
- npm install --save-dev es-module-lexer magic-string
241
- ```
242
-
243
- ## Extending & Contributing
181
+ ```bash
182
+ npm run build
183
+ npm publish
184
+ ```
244
185
 
245
- - Add plugins under `src/plugins` with `configResolved` and `transform` hooks.
246
- - 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
247
190
 
248
- ## Contributing
191
+ ---
249
192
 
250
- 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
251
194
 
252
- - [Contributing Guide](./CONTRIBUTING.md)
195
+ Found an issue or have a feature request?
196
+ šŸ‘‰ [Open an issue](https://github.com/venkateshsundaram/react-client/issues)
253
197
 
254
- ## Publishing
198
+ ---
255
199
 
256
- Before pushing your changes to Github, make sure that `version` in `package.json` is changed to newest version. Then run `npm install` to synchronize `package-lock.json`
200
+ ## 🪪 License
257
201
 
258
- ## Feedbacks and Issues
202
+ **MIT Licensed** Ā© [Venkatesh Sundaram](https://github.com/venkateshsundaram)
259
203
 
260
- 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
+ ---
261
205
 
262
- ## License
206
+ ## šŸ—ŗļø Architecture Overview (Bonus)
263
207
 
264
- 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
  }