react-client 1.0.7 ā 1.0.9
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 +141 -185
- package/dist/cli/commands/build.js +30 -47
- package/dist/cli/commands/build.ssr.js +31 -54
- package/dist/cli/commands/dev.js +221 -6
- package/dist/cli/commands/init.js +79 -48
- package/dist/cli/commands/preview.js +44 -16
- package/dist/cli/index.js +104 -18
- package/dist/utils/loadConfig.js +109 -0
- package/package.json +77 -34
- package/templates/react/index.html +1 -1
- package/templates/react-ssr/index.html +1 -1
- package/templates/react-ssr-ts/index.html +1 -1
- package/templates/react-tailwind/index.html +1 -1
- package/templates/react-tailwind-ts/index.html +1 -1
- package/templates/react-ts/index.html +1 -1
package/README.md
CHANGED
|
@@ -2,31 +2,30 @@
|
|
|
2
2
|
[](https://npm-stat.com/charts.html?package=react-client)
|
|
3
3
|
[](https://github.com/venkateshsundaram/react-client/issues)
|
|
4
4
|
|
|
5
|
-
react-client is a
|
|
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
|
-
|
|
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
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
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
|
-
- [
|
|
21
|
+
- [Feedback](#feedback)
|
|
23
22
|
- [License](#license)
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
---
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
## š§© Installation
|
|
28
27
|
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
---
|
|
42
45
|
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
+
Creates:
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
```js
|
|
57
|
+
// react-client.config.js
|
|
58
|
+
import { defineConfig } from 'react-client/config';
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
export default defineConfig({
|
|
61
|
+
root: './src',
|
|
62
|
+
server: { port: 5173 },
|
|
63
|
+
build: { outDir: '.react-client/build' }
|
|
64
|
+
});
|
|
65
|
+
```
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
71
|
+
---
|
|
67
72
|
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
Each template is pre-configured for esbuild, HMR, and fast bootstrapping.
|
|
79
85
|
|
|
80
|
-
|
|
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
|
-
|
|
88
|
+
## š Core Features
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
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`
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
root: '.',
|
|
93
|
-
server: { port: 3000 },
|
|
94
|
-
build: { outDir: 'dist/client' }
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### react-ts
|
|
101
|
+
---
|
|
99
102
|
|
|
100
|
-
|
|
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
|
-
|
|
105
|
+
**Under the hood:**
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
## š§Ŗ Local Development
|
|
121
116
|
|
|
122
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
127
|
+
If you run it from inside the CLI repo, it auto-detects and switches to `myapp/` as the root.
|
|
145
128
|
|
|
146
|
-
|
|
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
|
-
|
|
131
|
+
## š§© Troubleshooting
|
|
154
132
|
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
161
|
+
## š§āš» Contributing
|
|
201
162
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
import { defineConfig } from 'react-client'
|
|
163
|
+
We welcome contributions!
|
|
164
|
+
Read the [Contributing Guide](./CONTRIBUTING.md) for setup instructions.
|
|
205
165
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
246
|
-
-
|
|
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
|
-
|
|
191
|
+
---
|
|
249
192
|
|
|
250
|
-
|
|
193
|
+
## š¬ Feedback
|
|
251
194
|
|
|
252
|
-
|
|
195
|
+
Found an issue or have a feature request?
|
|
196
|
+
š [Open an issue](https://github.com/venkateshsundaram/react-client/issues)
|
|
253
197
|
|
|
254
|
-
|
|
198
|
+
---
|
|
255
199
|
|
|
256
|
-
|
|
200
|
+
## šŖŖ License
|
|
257
201
|
|
|
258
|
-
|
|
202
|
+
**MIT Licensed** Ā© [Venkatesh Sundaram](https://github.com/venkateshsundaram)
|
|
259
203
|
|
|
260
|
-
|
|
204
|
+
---
|
|
261
205
|
|
|
262
|
-
##
|
|
206
|
+
## šŗļø Architecture Overview (Bonus)
|
|
263
207
|
|
|
264
|
-
|
|
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
|
|
11
|
-
const
|
|
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
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
11
|
-
|
|
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
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'.
|
|
33
|
-
'.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
}
|