@webtypen/webframez-react 0.0.2 → 0.0.3
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 +214 -109
- package/bin/webframez-react.mjs +158 -7
- package/defaults/tsconfig.server.example.json +46 -0
- package/defaults/tsconfig.server.json +13 -0
- package/defaults/webpack.client.cjs +4 -1
- package/defaults/webpack.server.cjs +8 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/http.cjs +35 -11
- package/dist/http.d.ts +18 -7
- package/dist/http.js +35 -11
- package/dist/index.cjs +35 -11
- package/dist/index.d.ts +2 -5
- package/dist/index.js +35 -11
- package/dist/router.cjs +30 -10
- package/dist/router.js +30 -10
- package/dist/types.d.ts +25 -8
- package/dist/webframez-core.cjs +35 -11
- package/dist/webframez-core.d.ts +32 -8
- package/dist/webframez-core.js +35 -11
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# webframez-react
|
|
1
|
+
# @webtypen/webframez-react
|
|
2
2
|
|
|
3
3
|
React Server Components (RSC) extension for `@webtypen/webframez-core`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
`@webtypen/webframez-react` provides:
|
|
7
6
|
- seamless `webframez-core` integration via `initWebframezReact(Route)`
|
|
8
7
|
- file-based routing for `pages/**/index.tsx`
|
|
9
8
|
- layout/error handling with `RouteChildren`
|
|
10
9
|
- client-side navigation (`Link`, `Redirect`, `useRouter`) and cookies (`useCookie`)
|
|
11
10
|
- server-rendered initial HTML plus RSC streaming
|
|
11
|
+
- a small CLI for the standard server/client build pipeline
|
|
12
12
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
@@ -26,111 +26,201 @@ npm i -D typescript webpack webpack-cli ts-loader
|
|
|
26
26
|
## Quick Start with webframez-core
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
|
-
// server.ts
|
|
29
|
+
// src/server.ts
|
|
30
|
+
import path from "node:path";
|
|
30
31
|
import { BaseKernelWeb, Route, WebApplication } from "@webtypen/webframez-core";
|
|
31
|
-
import { initWebframezReact } from "@webtypen/webframez-react
|
|
32
|
+
import { initWebframezReact } from "@webtypen/webframez-react";
|
|
32
33
|
|
|
33
34
|
class Kernel extends BaseKernelWeb {
|
|
34
35
|
static controller = {};
|
|
35
36
|
static middleware = {};
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
initWebframezReact(Route);
|
|
39
|
+
const ReactRoute = initWebframezReact(Route);
|
|
39
40
|
|
|
40
41
|
const app = new WebApplication();
|
|
41
42
|
app.boot({
|
|
42
43
|
kernel: Kernel,
|
|
43
44
|
routesFunction: () => {
|
|
44
|
-
|
|
45
|
-
distRootDir:
|
|
45
|
+
ReactRoute.renderReact("/react", {
|
|
46
|
+
distRootDir: path.resolve(process.cwd(), "dist"),
|
|
46
47
|
});
|
|
47
48
|
},
|
|
48
49
|
});
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
Notes:
|
|
52
|
-
|
|
53
53
|
- `"/react"` is automatically registered as a catch-all route (`/react/*`).
|
|
54
|
-
- `basePath`, `assetsPrefix`, `rscPath`, and `clientScriptUrl` are derived automatically from the mount path.
|
|
54
|
+
- `basePath`, `assetsPrefix`, `rscPath`, and `clientScriptUrl` are derived automatically from the mount path unless you override them.
|
|
55
|
+
- `initWebframezReact(Route)` returns the extended route facade, which gives you reliable editor autocompletion for `renderReact(...)` even in monorepos or symlinked development setups.
|
|
56
|
+
- The package also ships module augmentation for `Route.renderReact(...)`, but using the returned `ReactRoute` variable is the most robust TypeScript setup.
|
|
55
57
|
|
|
56
|
-
##
|
|
58
|
+
## `Route.renderReact()`
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
Signature:
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm i @webtypen/webframez-react react react-dom react-server-dom-webpack
|
|
64
|
-
npm i -D typescript webpack webpack-cli ts-loader
|
|
62
|
+
```ts
|
|
63
|
+
Route.renderReact(path, options)
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
Example:
|
|
68
67
|
|
|
69
68
|
```ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class Kernel extends BaseKernelWeb {
|
|
76
|
-
static controller = {};
|
|
77
|
-
static middleware = {};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
initWebframezReact(Route);
|
|
81
|
-
|
|
82
|
-
const app = new WebApplication();
|
|
83
|
-
app.boot({
|
|
84
|
-
kernel: Kernel,
|
|
85
|
-
port: 3000,
|
|
86
|
-
routesFunction: () => {
|
|
87
|
-
// Your existing core routes can stay here.
|
|
88
|
-
Route.renderReact("/app", {
|
|
89
|
-
distRootDir: path.resolve(process.cwd(), "dist"),
|
|
90
|
-
});
|
|
69
|
+
Route.renderReact("/app", {
|
|
70
|
+
distRootDir: path.resolve(process.cwd(), "dist"),
|
|
71
|
+
method: "GET",
|
|
72
|
+
routeOptions: {
|
|
73
|
+
middleware: ["auth"],
|
|
91
74
|
},
|
|
92
75
|
});
|
|
93
76
|
```
|
|
94
77
|
|
|
95
|
-
|
|
78
|
+
### Options
|
|
79
|
+
|
|
80
|
+
`distRootDir`
|
|
81
|
+
- Required.
|
|
82
|
+
- Directory containing the built client assets and generated manifests.
|
|
83
|
+
|
|
84
|
+
`pagesDir`
|
|
85
|
+
- Optional.
|
|
86
|
+
- Directory containing the compiled `pages/**` output.
|
|
87
|
+
- Default: `${distRootDir}/pages`
|
|
88
|
+
|
|
89
|
+
`manifestPath`
|
|
90
|
+
- Optional.
|
|
91
|
+
- Path to the React client manifest.
|
|
92
|
+
- Default: `${distRootDir}/react-client-manifest.json`
|
|
93
|
+
|
|
94
|
+
`assetsPrefix`
|
|
95
|
+
- Optional.
|
|
96
|
+
- Public URL prefix used to serve built client assets.
|
|
97
|
+
- Auto-derived from `path`.
|
|
98
|
+
- Example for `"/react"`: `/react/assets/`
|
|
99
|
+
|
|
100
|
+
`rscPath`
|
|
101
|
+
- Optional.
|
|
102
|
+
- Public URL for the RSC endpoint.
|
|
103
|
+
- Auto-derived from `path`.
|
|
104
|
+
- Example for `"/react"`: `/react/rsc`
|
|
105
|
+
|
|
106
|
+
`clientScriptUrl`
|
|
107
|
+
- Optional.
|
|
108
|
+
- Public URL of the browser client entry bundle.
|
|
109
|
+
- Auto-derived from `path`.
|
|
110
|
+
- Example for `"/react"`: `/react/assets/client.js`
|
|
111
|
+
|
|
112
|
+
`basePath`
|
|
113
|
+
- Optional.
|
|
114
|
+
- Basename mounted in front of all file-router paths.
|
|
115
|
+
- Auto-derived from `path` when `path !== "/"`.
|
|
116
|
+
|
|
117
|
+
`liveReloadPath`
|
|
118
|
+
- Optional.
|
|
119
|
+
- Enables dev live reload on a custom path or disables it explicitly with `false`.
|
|
120
|
+
- Automatically disabled in production mode.
|
|
121
|
+
|
|
122
|
+
`method`
|
|
123
|
+
- Optional.
|
|
124
|
+
- HTTP method or methods used to register the route.
|
|
125
|
+
- Supported values: `"GET" | "POST" | "PUT" | "DELETE"`
|
|
126
|
+
- Default: `"GET"`
|
|
127
|
+
|
|
128
|
+
`routeOptions`
|
|
129
|
+
- Optional.
|
|
130
|
+
- Additional route options forwarded to `webframez-core`.
|
|
131
|
+
- Typical use case: middleware.
|
|
132
|
+
|
|
133
|
+
## Recommended Project Structure
|
|
96
134
|
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
|
|
135
|
+
```txt
|
|
136
|
+
pages/
|
|
137
|
+
layout.tsx
|
|
138
|
+
errors.tsx
|
|
139
|
+
index.tsx
|
|
140
|
+
about/index.tsx
|
|
141
|
+
src/
|
|
142
|
+
server.ts
|
|
143
|
+
client.tsx
|
|
144
|
+
components/
|
|
145
|
+
dist/
|
|
146
|
+
```
|
|
100
147
|
|
|
101
|
-
|
|
102
|
-
import { RouteChildren } from "@webtypen/webframez-react/router";
|
|
148
|
+
## `tsconfig.server.json`
|
|
103
149
|
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<main>
|
|
107
|
-
<RouteChildren />
|
|
108
|
-
</main>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
```
|
|
150
|
+
Compared to a standard `@webtypen/webframez-core` project, the server TypeScript config usually needs a few changes:
|
|
112
151
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
- enable JSX via `"jsx": "react-jsx"`
|
|
153
|
+
- include `pages/**/*.tsx`
|
|
154
|
+
- include `src/components/**/*.tsx`
|
|
155
|
+
- include your server entry (`src/server.ts` or `src/server.tsx`)
|
|
156
|
+
- exclude the browser client entry (`src/client.tsx` by default)
|
|
157
|
+
- add `paths` mappings for the `@webtypen/webframez-react` package and its subpaths
|
|
116
158
|
|
|
117
|
-
|
|
159
|
+
When you use the CLI (`webframez-react build:server` / `watch:server`), it generates a temporary `.webframez-react.tsconfig.server.json` that extends your project `tsconfig.server.json`. That means:
|
|
118
160
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```
|
|
161
|
+
- your own `tsconfig.server.json` stays the source of truth
|
|
162
|
+
- the CLI only injects the resolved server entry and the standard RSC include/exclude rules
|
|
163
|
+
- you only need to customize the base config when your project structure differs from the defaults
|
|
123
164
|
|
|
124
|
-
|
|
165
|
+
A good starting point is the shipped default config:
|
|
125
166
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
import { mountWebframezClient } from "@webtypen/webframez-react/client";
|
|
167
|
+
- `@webtypen/webframez-react/defaults/tsconfig.server`
|
|
168
|
+
- `@webtypen/webframez-react/defaults/tsconfig.server.example`
|
|
129
169
|
|
|
130
|
-
|
|
170
|
+
Example:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"compilerOptions": {
|
|
175
|
+
"target": "ES2020",
|
|
176
|
+
"module": "CommonJS",
|
|
177
|
+
"moduleResolution": "Node",
|
|
178
|
+
"baseUrl": ".",
|
|
179
|
+
"paths": {
|
|
180
|
+
"@webtypen/webframez-react": [
|
|
181
|
+
"./node_modules/@webtypen/webframez-react/dist/index.d.ts"
|
|
182
|
+
],
|
|
183
|
+
"@webtypen/webframez-react/types": [
|
|
184
|
+
"./node_modules/@webtypen/webframez-react/dist/types.d.ts"
|
|
185
|
+
],
|
|
186
|
+
"@webtypen/webframez-react/router": [
|
|
187
|
+
"./node_modules/@webtypen/webframez-react/dist/router.d.ts"
|
|
188
|
+
],
|
|
189
|
+
"@webtypen/webframez-react/client": [
|
|
190
|
+
"./node_modules/@webtypen/webframez-react/dist/client.d.ts"
|
|
191
|
+
],
|
|
192
|
+
"@webtypen/webframez-react/navigation": [
|
|
193
|
+
"./node_modules/@webtypen/webframez-react/dist/navigation.d.ts"
|
|
194
|
+
],
|
|
195
|
+
"@webtypen/webframez-react/webframez-core": [
|
|
196
|
+
"./node_modules/@webtypen/webframez-react/dist/webframez-core.d.ts"
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
"jsx": "react-jsx",
|
|
200
|
+
"strict": true,
|
|
201
|
+
"esModuleInterop": true,
|
|
202
|
+
"skipLibCheck": true,
|
|
203
|
+
"outDir": "dist",
|
|
204
|
+
"rootDir": "."
|
|
205
|
+
},
|
|
206
|
+
"include": [
|
|
207
|
+
"src/server.ts",
|
|
208
|
+
"src/server.tsx",
|
|
209
|
+
"src/components/**/*.tsx",
|
|
210
|
+
"pages/**/*.tsx",
|
|
211
|
+
"src/types.d.ts"
|
|
212
|
+
],
|
|
213
|
+
"exclude": [
|
|
214
|
+
"src/client.tsx",
|
|
215
|
+
"dist",
|
|
216
|
+
"node_modules"
|
|
217
|
+
]
|
|
218
|
+
}
|
|
131
219
|
```
|
|
132
220
|
|
|
133
|
-
|
|
221
|
+
## `package.json` Scripts
|
|
222
|
+
|
|
223
|
+
Recommended scripts:
|
|
134
224
|
|
|
135
225
|
```json
|
|
136
226
|
{
|
|
@@ -138,6 +228,7 @@ mountWebframezClient();
|
|
|
138
228
|
"build:server": "webframez-react build:server",
|
|
139
229
|
"build:client": "webframez-react build:client",
|
|
140
230
|
"build": "npm run build:server && npm run build:client",
|
|
231
|
+
"start": "node --conditions react-server start-server.cjs",
|
|
141
232
|
"watch:server": "webframez-react watch:server",
|
|
142
233
|
"watch:client": "webframez-react watch:client",
|
|
143
234
|
"serve:watch": "node --watch --conditions react-server start-server.cjs",
|
|
@@ -147,9 +238,51 @@ mountWebframezClient();
|
|
|
147
238
|
}
|
|
148
239
|
```
|
|
149
240
|
|
|
150
|
-
|
|
241
|
+
Notes:
|
|
242
|
+
- `build` compiles the server output (`pages`, `server.ts`) and the browser client bundle (`client.tsx` + RSC manifests).
|
|
243
|
+
- `start` runs the built app in React Server mode.
|
|
244
|
+
- `watch` / `dev` keep TypeScript and webpack in watch mode and restart Node automatically when server output changes.
|
|
245
|
+
|
|
246
|
+
## CLI Config and Custom Entry Paths
|
|
247
|
+
|
|
248
|
+
The CLI first checks project override files and then falls back to the package defaults:
|
|
249
|
+
|
|
250
|
+
- `tsconfig.server.json`
|
|
251
|
+
- `webpack.client.cjs`
|
|
252
|
+
- `webpack.server.cjs`
|
|
253
|
+
|
|
254
|
+
If you do not want to create your own webpack config just to move `client.tsx` or `server.ts`, you can use a small project config file:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
// webframez-react.config.mjs
|
|
258
|
+
export default {
|
|
259
|
+
clientEntryPath: "src/app/client.tsx",
|
|
260
|
+
serverEntryPath: "src/app/server.tsx",
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Supported file names:
|
|
265
|
+
- `webframez-react.config.mjs`
|
|
266
|
+
- `webframez-react.config.cjs`
|
|
267
|
+
- `webframez-react.config.js`
|
|
268
|
+
- `webframez-react.config.json`
|
|
269
|
+
|
|
270
|
+
You can also override the entry paths per command:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
webframez-react build:client --client-entry=src/app/client.tsx
|
|
274
|
+
webframez-react build:server --server-entry=src/app/server.tsx
|
|
275
|
+
webframez-react watch:client --client-entry=src/app/client.tsx
|
|
276
|
+
webframez-react watch:server --server-entry=src/app/server.tsx
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Notes:
|
|
280
|
+
- The default client entry is `src/client.tsx`.
|
|
281
|
+
- The default server entry is `src/server.ts`, with automatic fallback to `src/server.tsx` if present.
|
|
282
|
+
- `build:server:webpack` and `watch:server:webpack` also respect `serverEntryPath`.
|
|
283
|
+
- `build:server` and `watch:server` generate a temporary `.webframez-react.tsconfig.server.json` so custom server entry paths also work with the TypeScript compiler.
|
|
151
284
|
|
|
152
|
-
##
|
|
285
|
+
## File-Based Routing
|
|
153
286
|
|
|
154
287
|
Example:
|
|
155
288
|
|
|
@@ -205,9 +338,8 @@ export default function AccountPage({ params, abort }: PageProps) {
|
|
|
205
338
|
```
|
|
206
339
|
|
|
207
340
|
Behavior:
|
|
208
|
-
|
|
209
341
|
- default without options: `404` + `"Page not found"`
|
|
210
|
-
- rendered through `pages/errors.tsx`
|
|
342
|
+
- rendered through `pages/errors.tsx`
|
|
211
343
|
- `pathname` is provided automatically by context
|
|
212
344
|
- optional `payload` is forwarded to `errors.tsx`
|
|
213
345
|
|
|
@@ -255,8 +387,7 @@ export function Guard({ loggedIn }: { loggedIn: boolean }) {
|
|
|
255
387
|
}
|
|
256
388
|
```
|
|
257
389
|
|
|
258
|
-
|
|
259
|
-
|
|
390
|
+
Notes:
|
|
260
391
|
- `Link` and `Redirect` automatically use the basename from `Route.renderReact()`.
|
|
261
392
|
- You can override it per usage via `basename`.
|
|
262
393
|
|
|
@@ -288,7 +419,12 @@ export default function LoginAction() {
|
|
|
288
419
|
## Public Entrypoints
|
|
289
420
|
|
|
290
421
|
- `@webtypen/webframez-react`
|
|
291
|
-
- `
|
|
422
|
+
- `initWebframezReact`
|
|
423
|
+
- `createNodeRequestHandler`
|
|
424
|
+
- `createFileRouter`
|
|
425
|
+
- `createHTMLShell`
|
|
426
|
+
- `sendRSC`
|
|
427
|
+
- `createRSCHandler`
|
|
292
428
|
- `@webtypen/webframez-react/webframez-core`
|
|
293
429
|
- `initWebframezReact`
|
|
294
430
|
- `@webtypen/webframez-react/router`
|
|
@@ -298,48 +434,17 @@ export default function LoginAction() {
|
|
|
298
434
|
- `@webtypen/webframez-react/navigation`
|
|
299
435
|
- `Link`, `Redirect`
|
|
300
436
|
- `@webtypen/webframez-react/types`
|
|
301
|
-
-
|
|
437
|
+
- `RouteContext`, `PageProps`, `ErrorPageProps`, `AbortRouteOptions`, ...
|
|
302
438
|
|
|
303
|
-
##
|
|
304
|
-
|
|
305
|
-
Example scripts for a `webframez-react` app:
|
|
306
|
-
|
|
307
|
-
```json
|
|
308
|
-
{
|
|
309
|
-
"scripts": {
|
|
310
|
-
"build:server": "webframez-react build:server",
|
|
311
|
-
"build:client": "webframez-react build:client",
|
|
312
|
-
"build": "npm run build:server && npm run build:client",
|
|
313
|
-
"start": "node --conditions react-server start-server.cjs",
|
|
314
|
-
"watch:server": "webframez-react watch:server",
|
|
315
|
-
"watch:client": "webframez-react watch:client",
|
|
316
|
-
"serve:watch": "node --watch --conditions react-server start-server.cjs",
|
|
317
|
-
"watch": "sh -c 'npm run watch:server & npm run watch:client & npm run serve:watch & wait'",
|
|
318
|
-
"dev": "sh -c 'npm run watch:server & npm run watch:client & npm run serve:watch & wait'"
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
Notes:
|
|
324
|
-
|
|
325
|
-
- `webframez-react` CLI first checks project overrides and falls back to package defaults:
|
|
326
|
-
- `tsconfig.server.json`
|
|
327
|
-
- `webpack.client.cjs`
|
|
328
|
-
- `webpack.server.cjs`
|
|
329
|
-
- `webpack.server.cjs` is optional. Default flow compiles server with `tsc`. Use webpack-server only if you explicitly want a bundled server build:
|
|
330
|
-
- `webframez-react build:server:webpack`
|
|
331
|
-
- `webframez-react watch:server:webpack`
|
|
332
|
-
- `build` compiles server output (`pages`, `server.ts`) and client output (`client.tsx` bundle + RSC manifests).
|
|
333
|
-
- `start` runs the built app in React Server mode.
|
|
334
|
-
- `watch` and `dev` run the same full watch pipeline and restart Node automatically on server output changes.
|
|
439
|
+
## Package Build
|
|
335
440
|
|
|
336
|
-
|
|
441
|
+
Build the package itself:
|
|
337
442
|
|
|
338
443
|
```bash
|
|
339
444
|
npm run build
|
|
340
445
|
```
|
|
341
446
|
|
|
342
|
-
Watch mode:
|
|
447
|
+
Watch mode for package development:
|
|
343
448
|
|
|
344
449
|
```bash
|
|
345
450
|
npm run build:watch
|
package/bin/webframez-react.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import fsp from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
8
|
|
|
8
9
|
const binFilePath = fileURLToPath(import.meta.url);
|
|
9
10
|
const packageRoot = path.resolve(path.dirname(binFilePath), "..");
|
|
@@ -12,6 +13,7 @@ const projectRoot = process.cwd();
|
|
|
12
13
|
const command = process.argv[2];
|
|
13
14
|
const passthroughStart = process.argv[3] === "--" ? 4 : 3;
|
|
14
15
|
const passthroughArgs = process.argv.slice(passthroughStart);
|
|
16
|
+
const customArgPrefixes = ["--client-entry", "--server-entry"];
|
|
15
17
|
|
|
16
18
|
function printHelp() {
|
|
17
19
|
console.log(
|
|
@@ -34,6 +36,13 @@ function printHelp() {
|
|
|
34
36
|
" - tsconfig.server.json",
|
|
35
37
|
" - webpack.client.cjs",
|
|
36
38
|
" - webpack.server.cjs",
|
|
39
|
+
"",
|
|
40
|
+
"Optional project config file:",
|
|
41
|
+
" - webframez-react.config.mjs|cjs|js|json",
|
|
42
|
+
"",
|
|
43
|
+
"Optional CLI overrides:",
|
|
44
|
+
" --client-entry=src/client.tsx",
|
|
45
|
+
" --server-entry=src/server.ts",
|
|
37
46
|
].join("\n"),
|
|
38
47
|
);
|
|
39
48
|
}
|
|
@@ -42,6 +51,43 @@ function hasFlag(flag) {
|
|
|
42
51
|
return passthroughArgs.includes(flag);
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
function normalizeRelativePath(value) {
|
|
55
|
+
return value.replace(/\\/g, "/");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readCustomArg(name) {
|
|
59
|
+
const withEquals = `${name}=`;
|
|
60
|
+
for (let index = 0; index < passthroughArgs.length; index += 1) {
|
|
61
|
+
const value = passthroughArgs[index];
|
|
62
|
+
if (value === name) {
|
|
63
|
+
return passthroughArgs[index + 1] || null;
|
|
64
|
+
}
|
|
65
|
+
if (value.startsWith(withEquals)) {
|
|
66
|
+
return value.slice(withEquals.length);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function stripCustomArgs(args) {
|
|
73
|
+
const filtered = [];
|
|
74
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
75
|
+
const value = args[index];
|
|
76
|
+
const matchedPrefix = customArgPrefixes.find(
|
|
77
|
+
(prefix) => value === prefix || value.startsWith(`${prefix}=`),
|
|
78
|
+
);
|
|
79
|
+
if (!matchedPrefix) {
|
|
80
|
+
filtered.push(value);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (value === matchedPrefix) {
|
|
85
|
+
index += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return filtered;
|
|
89
|
+
}
|
|
90
|
+
|
|
45
91
|
function resolveConfig(localFileName, fallbackFileName) {
|
|
46
92
|
const localPath = path.resolve(projectRoot, localFileName);
|
|
47
93
|
if (fs.existsSync(localPath)) {
|
|
@@ -69,7 +115,78 @@ function resolveBinary(name) {
|
|
|
69
115
|
return name;
|
|
70
116
|
}
|
|
71
117
|
|
|
72
|
-
function
|
|
118
|
+
async function loadProjectConfig() {
|
|
119
|
+
const configFiles = [
|
|
120
|
+
"webframez-react.config.mjs",
|
|
121
|
+
"webframez-react.config.cjs",
|
|
122
|
+
"webframez-react.config.js",
|
|
123
|
+
"webframez-react.config.json",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const fileName of configFiles) {
|
|
127
|
+
const filePath = path.resolve(projectRoot, fileName);
|
|
128
|
+
if (!fs.existsSync(filePath)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (fileName.endsWith(".json")) {
|
|
133
|
+
return JSON.parse(await fsp.readFile(filePath, "utf8"));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const imported = await import(pathToFileURL(filePath).href);
|
|
137
|
+
return imported.default ?? imported;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveEntryPath(projectConfig, customArgValue, configKey, defaults) {
|
|
144
|
+
const configuredValue = customArgValue || projectConfig?.[configKey];
|
|
145
|
+
if (configuredValue && typeof configuredValue === "string") {
|
|
146
|
+
return path.resolve(projectRoot, configuredValue);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const defaultPath of defaults) {
|
|
150
|
+
const resolved = path.resolve(projectRoot, defaultPath);
|
|
151
|
+
if (fs.existsSync(resolved)) {
|
|
152
|
+
return resolved;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return path.resolve(projectRoot, defaults[0]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function createServerTsConfig(baseConfig, serverEntryPath, clientEntryPath) {
|
|
160
|
+
const generatedPath = path.resolve(projectRoot, ".webframez-react.tsconfig.server.json");
|
|
161
|
+
const extendsPath =
|
|
162
|
+
baseConfig.source === "project"
|
|
163
|
+
? `./${normalizeRelativePath(path.basename(baseConfig.path))}`
|
|
164
|
+
: normalizeRelativePath(path.relative(projectRoot, baseConfig.path));
|
|
165
|
+
|
|
166
|
+
const include = [
|
|
167
|
+
normalizeRelativePath(path.relative(projectRoot, serverEntryPath)),
|
|
168
|
+
"src/components/**/*.tsx",
|
|
169
|
+
"pages/**/*.tsx",
|
|
170
|
+
"src/types.d.ts",
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const exclude = [
|
|
174
|
+
normalizeRelativePath(path.relative(projectRoot, clientEntryPath)),
|
|
175
|
+
"dist",
|
|
176
|
+
"node_modules",
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const generatedConfig = {
|
|
180
|
+
extends: extendsPath,
|
|
181
|
+
include: Array.from(new Set(include)),
|
|
182
|
+
exclude: Array.from(new Set(exclude)),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
await fsp.writeFile(generatedPath, JSON.stringify(generatedConfig, null, 2));
|
|
186
|
+
return generatedPath;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function run(binaryName, args, envAdditions = {}) {
|
|
73
190
|
const binary = resolveBinary(binaryName);
|
|
74
191
|
|
|
75
192
|
return new Promise((resolve, reject) => {
|
|
@@ -77,6 +194,10 @@ function run(binaryName, args) {
|
|
|
77
194
|
cwd: projectRoot,
|
|
78
195
|
stdio: "inherit",
|
|
79
196
|
shell: false,
|
|
197
|
+
env: {
|
|
198
|
+
...process.env,
|
|
199
|
+
...envAdditions,
|
|
200
|
+
},
|
|
80
201
|
});
|
|
81
202
|
|
|
82
203
|
child.on("error", (error) => {
|
|
@@ -95,11 +216,35 @@ async function main() {
|
|
|
95
216
|
return;
|
|
96
217
|
}
|
|
97
218
|
|
|
219
|
+
const projectConfig = await loadProjectConfig();
|
|
220
|
+
const customClientEntry = readCustomArg("--client-entry");
|
|
221
|
+
const customServerEntry = readCustomArg("--server-entry");
|
|
222
|
+
const passthroughArgsClean = stripCustomArgs(passthroughArgs);
|
|
223
|
+
|
|
224
|
+
const clientEntryPath = resolveEntryPath(
|
|
225
|
+
projectConfig,
|
|
226
|
+
customClientEntry,
|
|
227
|
+
"clientEntryPath",
|
|
228
|
+
["src/client.tsx"],
|
|
229
|
+
);
|
|
230
|
+
const serverEntryPath = resolveEntryPath(
|
|
231
|
+
projectConfig,
|
|
232
|
+
customServerEntry,
|
|
233
|
+
"serverEntryPath",
|
|
234
|
+
["src/server.ts", "src/server.tsx"],
|
|
235
|
+
);
|
|
236
|
+
|
|
98
237
|
if (command === "build:server" || command === "watch:server") {
|
|
99
238
|
const config = resolveConfig("tsconfig.server.json", "tsconfig.server.json");
|
|
239
|
+
const generatedConfigPath = await createServerTsConfig(
|
|
240
|
+
config,
|
|
241
|
+
serverEntryPath,
|
|
242
|
+
clientEntryPath,
|
|
243
|
+
);
|
|
100
244
|
console.log(`[webframez-react] tsc config (${config.source}): ${config.path}`);
|
|
245
|
+
console.log(`[webframez-react] server entry: ${serverEntryPath}`);
|
|
101
246
|
|
|
102
|
-
const args = ["-p",
|
|
247
|
+
const args = ["-p", generatedConfigPath, ...passthroughArgsClean];
|
|
103
248
|
if (command === "watch:server") {
|
|
104
249
|
if (!hasFlag("--watch")) {
|
|
105
250
|
args.push("--watch");
|
|
@@ -117,13 +262,16 @@ async function main() {
|
|
|
117
262
|
if (command === "build:client" || command === "watch:client") {
|
|
118
263
|
const config = resolveConfig("webpack.client.cjs", "webpack.client.cjs");
|
|
119
264
|
console.log(`[webframez-react] webpack config (${config.source}): ${config.path}`);
|
|
265
|
+
console.log(`[webframez-react] client entry: ${clientEntryPath}`);
|
|
120
266
|
|
|
121
|
-
const args = ["--config", config.path, ...
|
|
267
|
+
const args = ["--config", config.path, ...passthroughArgsClean];
|
|
122
268
|
if (command === "watch:client" && !hasFlag("--watch")) {
|
|
123
269
|
args.push("--watch");
|
|
124
270
|
}
|
|
125
271
|
|
|
126
|
-
const code = await run("webpack", args
|
|
272
|
+
const code = await run("webpack", args, {
|
|
273
|
+
WEBFRAMEZ_REACT_CLIENT_ENTRY: clientEntryPath,
|
|
274
|
+
});
|
|
127
275
|
process.exit(code);
|
|
128
276
|
return;
|
|
129
277
|
}
|
|
@@ -131,13 +279,16 @@ async function main() {
|
|
|
131
279
|
if (command === "build:server:webpack" || command === "watch:server:webpack") {
|
|
132
280
|
const config = resolveConfig("webpack.server.cjs", "webpack.server.cjs");
|
|
133
281
|
console.log(`[webframez-react] webpack server config (${config.source}): ${config.path}`);
|
|
282
|
+
console.log(`[webframez-react] server entry: ${serverEntryPath}`);
|
|
134
283
|
|
|
135
|
-
const args = ["--config", config.path, ...
|
|
284
|
+
const args = ["--config", config.path, ...passthroughArgsClean];
|
|
136
285
|
if (command === "watch:server:webpack" && !hasFlag("--watch")) {
|
|
137
286
|
args.push("--watch");
|
|
138
287
|
}
|
|
139
288
|
|
|
140
|
-
const code = await run("webpack", args
|
|
289
|
+
const code = await run("webpack", args, {
|
|
290
|
+
WEBFRAMEZ_REACT_SERVER_ENTRY: serverEntryPath,
|
|
291
|
+
});
|
|
141
292
|
process.exit(code);
|
|
142
293
|
return;
|
|
143
294
|
}
|