@taujs/create-taujs 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +275 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,11 +45,15 @@ var PACKAGE_MANAGERS = {
|
|
|
45
45
|
pnpm: "pnpm install",
|
|
46
46
|
yarn: "yarn install"
|
|
47
47
|
};
|
|
48
|
-
var FRAMEWORKS = ["react", "vue"];
|
|
48
|
+
var FRAMEWORKS = ["react", "vue", "solid"];
|
|
49
|
+
var PACKAGE_MANAGER_NAMES = Object.keys(PACKAGE_MANAGERS);
|
|
49
50
|
function parseArgs() {
|
|
50
51
|
const rawArgs = process.argv.slice(2);
|
|
51
52
|
let projectName;
|
|
52
53
|
let framework;
|
|
54
|
+
let packageManager;
|
|
55
|
+
let sawInstall = false;
|
|
56
|
+
let sawNoInstall = false;
|
|
53
57
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
54
58
|
const arg = rawArgs[i];
|
|
55
59
|
if (arg === "--framework") {
|
|
@@ -60,12 +64,37 @@ function parseArgs() {
|
|
|
60
64
|
framework = arg.slice("--framework=".length);
|
|
61
65
|
continue;
|
|
62
66
|
}
|
|
67
|
+
if (arg === "--package-manager") {
|
|
68
|
+
packageManager = rawArgs[++i];
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg.startsWith("--package-manager=")) {
|
|
72
|
+
packageManager = arg.slice("--package-manager=".length);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === "--install") {
|
|
76
|
+
sawInstall = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (arg === "--no-install") {
|
|
80
|
+
sawNoInstall = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
63
83
|
if (!arg.startsWith("-") && !projectName) {
|
|
64
84
|
projectName = arg;
|
|
65
85
|
continue;
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
projectName,
|
|
90
|
+
framework,
|
|
91
|
+
packageManager,
|
|
92
|
+
install: sawInstall && !sawNoInstall ? true : sawNoInstall && !sawInstall ? false : void 0,
|
|
93
|
+
installConflict: sawInstall && sawNoInstall
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function validatePackageManager(value) {
|
|
97
|
+
return PACKAGE_MANAGER_NAMES.includes(value) ? true : `Package manager must be one of: ${PACKAGE_MANAGER_NAMES.join(", ")}`;
|
|
69
98
|
}
|
|
70
99
|
function validateProjectName(value) {
|
|
71
100
|
if (!value) return "Project name is required";
|
|
@@ -79,7 +108,19 @@ function validateFramework(value) {
|
|
|
79
108
|
}
|
|
80
109
|
async function main() {
|
|
81
110
|
console.log(pc.cyan("\nWelcome to \u03C4js (taujs)\n"));
|
|
82
|
-
const { projectName: argName, framework: argFramework } = parseArgs();
|
|
111
|
+
const { projectName: argName, framework: argFramework, packageManager: argPackageManager, install: argInstall, installConflict } = parseArgs();
|
|
112
|
+
if (installConflict) {
|
|
113
|
+
console.log(pc.red("\n\u2716 --install and --no-install are mutually exclusive"));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
if (argPackageManager) {
|
|
117
|
+
const res = validatePackageManager(argPackageManager);
|
|
118
|
+
if (res !== true) {
|
|
119
|
+
console.log(pc.red(`
|
|
120
|
+
\u2716 Invalid package manager "${argPackageManager}": ${res}`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
83
124
|
if (argName) {
|
|
84
125
|
const res = validateProjectName(argName);
|
|
85
126
|
if (res !== true) {
|
|
@@ -110,12 +151,13 @@ async function main() {
|
|
|
110
151
|
message: "Framework:",
|
|
111
152
|
choices: [
|
|
112
153
|
{ title: "React", value: "react" },
|
|
113
|
-
{ title: "Vue", value: "vue" }
|
|
154
|
+
{ title: "Vue", value: "vue" },
|
|
155
|
+
{ title: "Solid", value: "solid" }
|
|
114
156
|
],
|
|
115
157
|
initial: 0
|
|
116
158
|
},
|
|
117
159
|
{
|
|
118
|
-
type: "select",
|
|
160
|
+
type: argPackageManager ? null : "select",
|
|
119
161
|
name: "packageManager",
|
|
120
162
|
message: "Package manager:",
|
|
121
163
|
choices: [
|
|
@@ -126,7 +168,7 @@ async function main() {
|
|
|
126
168
|
initial: 0
|
|
127
169
|
},
|
|
128
170
|
{
|
|
129
|
-
type: "confirm",
|
|
171
|
+
type: argInstall === void 0 ? "confirm" : null,
|
|
130
172
|
name: "installDeps",
|
|
131
173
|
message: "Install dependencies now?",
|
|
132
174
|
initial: true
|
|
@@ -158,8 +200,8 @@ async function main() {
|
|
|
158
200
|
}
|
|
159
201
|
const config = {
|
|
160
202
|
projectName,
|
|
161
|
-
packageManager: answers.packageManager,
|
|
162
|
-
installDeps: answers.installDeps,
|
|
203
|
+
packageManager: argPackageManager ?? answers.packageManager,
|
|
204
|
+
installDeps: argInstall ?? answers.installDeps,
|
|
163
205
|
framework
|
|
164
206
|
};
|
|
165
207
|
await createProject(config);
|
|
@@ -231,14 +273,27 @@ function planFiles(config) {
|
|
|
231
273
|
{ path: "CLAUDE.md", content: generateClaudeMd() },
|
|
232
274
|
{ path: "src/client/index.html", content: generateIndexHtml() },
|
|
233
275
|
{ path: "src/client/styles.css", content: generateStyles() },
|
|
276
|
+
// The derived type contract (framework-independent): AppData/AppRouteContext come from
|
|
277
|
+
// taujs.config.ts, so client code never hand-writes a payload shape.
|
|
278
|
+
{ path: "src/client/app-types.ts", content: generateAppTypes() },
|
|
234
279
|
// server (framework-independent — a single shared source)
|
|
235
280
|
{ path: "src/server/index.ts", content: generateServerIndex() },
|
|
236
281
|
{ path: "src/server/services/registry.ts", content: generateServiceRegistry() },
|
|
237
282
|
{ path: "src/server/services/example.service.ts", content: generateExampleService() },
|
|
238
283
|
{ path: "src/server/types.d.ts", content: generateServiceTypesAugmentation() },
|
|
239
|
-
{ path: "src/client/public/favicon.svg", content: generateFavicon() }
|
|
284
|
+
{ path: "src/client/public/favicon.svg", content: generateFavicon() },
|
|
285
|
+
// Solid's managed compiler owns a DISJOINT tsconfig project: it must claim the client TSX and
|
|
286
|
+
// nothing else. Pointing it at the root tsconfig would make it claim `src/server/**` too, which
|
|
287
|
+
// is not Solid TSX and is compiled by the server toolchain.
|
|
288
|
+
...framework === "solid" ? [{ path: "tsconfig.solid.json", json: generateSolidCompilerTsConfig() }] : []
|
|
240
289
|
];
|
|
241
|
-
const client = framework === "
|
|
290
|
+
const client = framework === "solid" ? [
|
|
291
|
+
{ path: "src/client/App.tsx", content: generateAppComponentSolid() },
|
|
292
|
+
{ path: "src/client/renderId.ts", content: generateSolidRenderId() },
|
|
293
|
+
{ path: "src/client/entry-client.tsx", content: generateEntryClientSolid() },
|
|
294
|
+
{ path: "src/client/entry-server.tsx", content: generateEntryServerSolid() },
|
|
295
|
+
{ path: "src/client/vite-env.d.ts", content: generateViteEnv() }
|
|
296
|
+
] : framework === "vue" ? [
|
|
242
297
|
{ path: "src/client/App.vue", content: generateAppVue() },
|
|
243
298
|
{ path: "src/client/HomePage.vue", content: generateHomePageVue() },
|
|
244
299
|
{ path: "src/client/StreamingPage.vue", content: generateStreamingPageVue() },
|
|
@@ -273,10 +328,10 @@ function generatePackageJson(projectName, framework) {
|
|
|
273
328
|
type: "module",
|
|
274
329
|
scripts: {
|
|
275
330
|
dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
|
|
276
|
-
"build:client": "tsx build.ts",
|
|
277
|
-
"build:entry-server": "cross-env BUILD_MODE=ssr tsx build.ts",
|
|
331
|
+
"build:client": "cross-env NODE_ENV=production tsx build.ts",
|
|
332
|
+
"build:entry-server": "cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts",
|
|
278
333
|
"build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
|
|
279
|
-
build: "tsx build.ts && cross-env BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
|
|
334
|
+
build: "cross-env NODE_ENV=production tsx build.ts && cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
|
|
280
335
|
start: "cross-env NODE_ENV=production node dist/server/index.js",
|
|
281
336
|
lint: "vue-tsc --noEmit"
|
|
282
337
|
},
|
|
@@ -292,6 +347,9 @@ function generatePackageJson(projectName, framework) {
|
|
|
292
347
|
"@types/node": "^22.10.5",
|
|
293
348
|
"@vitejs/plugin-vue": "^6.0.0",
|
|
294
349
|
"cross-env": "^7.0.3",
|
|
350
|
+
// `build:server` invokes the esbuild BINARY directly, so it must be a declared dependency
|
|
351
|
+
// of the generated project - it is not inherited from vite's own copy.
|
|
352
|
+
esbuild: "^0.25.0",
|
|
295
353
|
tsx: "^4.19.3",
|
|
296
354
|
typescript: "^5.7.3",
|
|
297
355
|
vite: "^7.1.11",
|
|
@@ -299,6 +357,43 @@ function generatePackageJson(projectName, framework) {
|
|
|
299
357
|
}
|
|
300
358
|
};
|
|
301
359
|
}
|
|
360
|
+
if (framework === "solid") {
|
|
361
|
+
return {
|
|
362
|
+
name: projectName,
|
|
363
|
+
version: "0.1.0",
|
|
364
|
+
private: true,
|
|
365
|
+
type: "module",
|
|
366
|
+
scripts: {
|
|
367
|
+
dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
|
|
368
|
+
"build:client": "cross-env NODE_ENV=production tsx build.ts",
|
|
369
|
+
"build:entry-server": "cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts",
|
|
370
|
+
"build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/solid",
|
|
371
|
+
build: "cross-env NODE_ENV=production tsx build.ts && cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/solid",
|
|
372
|
+
start: "cross-env NODE_ENV=production node dist/server/index.js",
|
|
373
|
+
lint: "tsc --noEmit"
|
|
374
|
+
},
|
|
375
|
+
dependencies: {
|
|
376
|
+
"@taujs/server": "latest",
|
|
377
|
+
"@taujs/solid": "latest",
|
|
378
|
+
fastify: "^5.2.0",
|
|
379
|
+
"solid-js": "^1.9.0"
|
|
380
|
+
},
|
|
381
|
+
devDependencies: {
|
|
382
|
+
"@taujs/mcp": "latest",
|
|
383
|
+
"@types/node": "^22.10.5",
|
|
384
|
+
"cross-env": "^7.0.3",
|
|
385
|
+
// `build:server` invokes the esbuild BINARY directly, so it must be a declared dependency
|
|
386
|
+
// of the generated project - it is not inherited from vite's own copy.
|
|
387
|
+
esbuild: "^0.25.0",
|
|
388
|
+
tsx: "^4.19.3",
|
|
389
|
+
typescript: "^5.7.3",
|
|
390
|
+
vite: "^7.1.11",
|
|
391
|
+
// The managed compiler instantiates this internally with `ssr: true` forced; the app never
|
|
392
|
+
// adds it to `plugins` itself.
|
|
393
|
+
"vite-plugin-solid": "^2.11.0"
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
302
397
|
return {
|
|
303
398
|
name: projectName,
|
|
304
399
|
version: "0.1.0",
|
|
@@ -306,10 +401,10 @@ function generatePackageJson(projectName, framework) {
|
|
|
306
401
|
type: "module",
|
|
307
402
|
scripts: {
|
|
308
403
|
dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
|
|
309
|
-
"build:client": "tsx build.ts",
|
|
310
|
-
"build:entry-server": "cross-env BUILD_MODE=ssr tsx build.ts",
|
|
404
|
+
"build:client": "cross-env NODE_ENV=production tsx build.ts",
|
|
405
|
+
"build:entry-server": "cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts",
|
|
311
406
|
"build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/react",
|
|
312
|
-
build: "tsx build.ts && cross-env BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/react",
|
|
407
|
+
build: "cross-env NODE_ENV=production tsx build.ts && cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/react",
|
|
313
408
|
start: "cross-env NODE_ENV=production node dist/server/index.js",
|
|
314
409
|
lint: "tsc --noEmit"
|
|
315
410
|
},
|
|
@@ -327,6 +422,9 @@ function generatePackageJson(projectName, framework) {
|
|
|
327
422
|
"@types/react-dom": "^19.0.2",
|
|
328
423
|
"@vitejs/plugin-react": "^4.6.0",
|
|
329
424
|
"cross-env": "^7.0.3",
|
|
425
|
+
// `build:server` invokes the esbuild BINARY directly, so it must be a declared dependency
|
|
426
|
+
// of the generated project - it is not inherited from vite's own copy.
|
|
427
|
+
esbuild: "^0.25.0",
|
|
330
428
|
tsx: "^4.19.3",
|
|
331
429
|
typescript: "^5.7.3",
|
|
332
430
|
vite: "^7.1.11"
|
|
@@ -351,8 +449,10 @@ function generateTsConfig(framework) {
|
|
|
351
449
|
target: "ES2022",
|
|
352
450
|
module: "ESNext",
|
|
353
451
|
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
354
|
-
// Vue SFCs are typed by vue-tsc; React needs the automatic JSX runtime
|
|
452
|
+
// Vue SFCs are typed by vue-tsc; React needs the automatic JSX runtime; Solid PRESERVES JSX
|
|
453
|
+
// for its own Babel transform and types it through `solid-js`.
|
|
355
454
|
...framework === "react" ? { jsx: "react-jsx" } : {},
|
|
455
|
+
...framework === "solid" ? { jsx: "preserve", jsxImportSource: "solid-js" } : {},
|
|
356
456
|
moduleResolution: "bundler",
|
|
357
457
|
resolveJsonModule: true,
|
|
358
458
|
allowImportingTsExtensions: true,
|
|
@@ -378,11 +478,23 @@ function generateServerTsConfig() {
|
|
|
378
478
|
};
|
|
379
479
|
}
|
|
380
480
|
function generateTaujsConfig(framework) {
|
|
381
|
-
const
|
|
382
|
-
import {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
481
|
+
const rendererImport = framework === "vue" ? `
|
|
482
|
+
import { vueRenderer } from '@taujs/vue/renderer';` : framework === "solid" ? `
|
|
483
|
+
import { solidRenderer } from '@taujs/solid/renderer';` : `
|
|
484
|
+
import { reactRenderer } from '@taujs/react/renderer';`;
|
|
485
|
+
const rendererLine = framework === "vue" ? `
|
|
486
|
+
renderer: vueRenderer(),` : framework === "solid" ? `
|
|
487
|
+
renderer: solidRenderer({ project: './tsconfig.solid.json' }),` : `
|
|
488
|
+
renderer: reactRenderer({ project: './tsconfig.json' }),`;
|
|
489
|
+
return `import { createServiceData, defineConfig } from '@taujs/server/config';${rendererImport}
|
|
490
|
+
|
|
491
|
+
import type { ServiceRegistry } from './src/server/services/registry.ts';
|
|
492
|
+
|
|
493
|
+
// Registry-typed service declarations: the helper carries each method's RESULT type into
|
|
494
|
+
// RouteData, so the app's types derive from this file instead of being hand-written
|
|
495
|
+
// (see src/client/app-types.ts). Closure handlers and ctx.call remain available for dynamic
|
|
496
|
+
// dispatch - https://taujs.dev/guides/services/
|
|
497
|
+
const serviceData = createServiceData<ServiceRegistry>();
|
|
386
498
|
|
|
387
499
|
export default defineConfig({
|
|
388
500
|
server: {
|
|
@@ -399,17 +511,15 @@ export default defineConfig({
|
|
|
399
511
|
apps: [
|
|
400
512
|
{
|
|
401
513
|
appId: 'main',
|
|
402
|
-
entryPoint: '',${
|
|
514
|
+
entryPoint: '',${rendererLine}
|
|
403
515
|
routes: [
|
|
404
516
|
{
|
|
405
517
|
path: '/',
|
|
406
518
|
attr: {
|
|
407
519
|
render: 'ssr',
|
|
408
520
|
hydrate: true,
|
|
409
|
-
//
|
|
410
|
-
data:
|
|
411
|
-
return ctx.call('example', 'greet', { name: 'SSR' });
|
|
412
|
-
},
|
|
521
|
+
// Declared service edge: RouteData<typeof config, '/'> is greet()'s resolved result
|
|
522
|
+
data: serviceData('example', 'greet', () => ({ name: 'SSR' })),
|
|
413
523
|
},
|
|
414
524
|
},
|
|
415
525
|
{
|
|
@@ -417,12 +527,8 @@ export default defineConfig({
|
|
|
417
527
|
attr: {
|
|
418
528
|
render: 'streaming',
|
|
419
529
|
hydrate: true,
|
|
420
|
-
//
|
|
421
|
-
data:
|
|
422
|
-
args: { name: 'Streaming' },
|
|
423
|
-
serviceName: 'example',
|
|
424
|
-
serviceMethod: 'greet',
|
|
425
|
-
}),
|
|
530
|
+
// The same declared edge on a streaming route: the shell streams while greet() resolves
|
|
531
|
+
data: serviceData('example', 'greet', () => ({ name: 'Streaming' })),
|
|
426
532
|
// meta recommended for streaming routes for SEO/social and render timing
|
|
427
533
|
meta: {
|
|
428
534
|
title: "\u03C4js \u2014 Streaming",
|
|
@@ -483,8 +589,10 @@ function generateReadme(projectName, packageManager, framework) {
|
|
|
483
589
|
const clientTree = framework === "vue" ? `\u2502 \u2502 \u251C\u2500\u2500 App.vue # Root component (route switch)
|
|
484
590
|
\u2502 \u2502 \u251C\u2500\u2500 HomePage.vue # SSR route (useSSRData + v-if)
|
|
485
591
|
\u2502 \u2502 \u251C\u2500\u2500 StreamingPage.vue # Streaming route (await useSSRDataAsync)
|
|
592
|
+
\u2502 \u2502 \u251C\u2500\u2500 app-types.ts # Types derived from taujs.config.ts
|
|
486
593
|
\u2502 \u2502 \u251C\u2500\u2500 entry-client.ts # Client hydration entry
|
|
487
594
|
\u2502 \u2502 \u251C\u2500\u2500 entry-server.ts # SSR render entry` : `\u2502 \u2502 \u251C\u2500\u2500 App.tsx # Root component
|
|
595
|
+
\u2502 \u2502 \u251C\u2500\u2500 app-types.ts # Types derived from taujs.config.ts
|
|
488
596
|
\u2502 \u2502 \u251C\u2500\u2500 entry-client.tsx # Client hydration entry
|
|
489
597
|
\u2502 \u2502 \u251C\u2500\u2500 entry-server.tsx # SSR render entry`;
|
|
490
598
|
const mainUi = framework === "vue" ? "App.vue" : "App.tsx";
|
|
@@ -579,13 +687,11 @@ import { useSSRStore } from '@taujs/react';
|
|
|
579
687
|
|
|
580
688
|
import "./styles.css";
|
|
581
689
|
|
|
582
|
-
type
|
|
583
|
-
message: string;
|
|
584
|
-
timestamp: string;
|
|
585
|
-
};
|
|
690
|
+
import type { AppData } from './app-types';
|
|
586
691
|
|
|
587
692
|
function GreetingCard() {
|
|
588
|
-
|
|
693
|
+
// AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
|
|
694
|
+
const data = useSSRStore<AppData>();
|
|
589
695
|
|
|
590
696
|
return (
|
|
591
697
|
<section className="card card--primary">
|
|
@@ -639,9 +745,9 @@ export function App() {
|
|
|
639
745
|
which is ideal for predictable latency and caching.
|
|
640
746
|
</p>
|
|
641
747
|
<p>
|
|
642
|
-
<strong>STREAM:</strong> The <code>/streaming</code> route
|
|
643
|
-
|
|
644
|
-
a fallback while the server resolves it, then progressively streams the final content.
|
|
748
|
+
<strong>STREAM:</strong> The <code>/streaming</code> route declares the same typed
|
|
749
|
+
service edge with streaming rendering. The <code><Suspense></code> boundary above
|
|
750
|
+
shows a fallback while the server resolves it, then progressively streams the final content.
|
|
645
751
|
</p>
|
|
646
752
|
</section>
|
|
647
753
|
|
|
@@ -879,6 +985,33 @@ code {
|
|
|
879
985
|
text-align: center;
|
|
880
986
|
}`;
|
|
881
987
|
}
|
|
988
|
+
function generateAppTypes() {
|
|
989
|
+
return `/**
|
|
990
|
+
* The application's type contract, DERIVED from taujs.config.ts - never hand-written.
|
|
991
|
+
* Every import here is type-only, so nothing from the config or server reaches the client bundle.
|
|
992
|
+
*/
|
|
993
|
+
import type config from '../../taujs.config.ts';
|
|
994
|
+
import type { RouteContext, RouteData } from '@taujs/server/config';
|
|
995
|
+
|
|
996
|
+
/** The route-discriminated context the host passes to appComponent and headContent. */
|
|
997
|
+
export type AppRouteContext = RouteContext<typeof config>;
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* The union of every declared route's resolved data - both scaffold routes resolve greet(), so
|
|
1001
|
+
* one shared type serves the whole app.
|
|
1002
|
+
*
|
|
1003
|
+
* A declared route WITHOUT \`attr.data\` contributes \`Record<string, undefined>\` (the server
|
|
1004
|
+
* supplies \`{}\` for it), so the union stays usable when such routes exist: reads become
|
|
1005
|
+
* \`T | undefined\`, making code account for the no-data route. When routes diverge further,
|
|
1006
|
+
* narrow per route with the derived aliases below - still from the config, never hand-written.
|
|
1007
|
+
*/
|
|
1008
|
+
export type AppData = RouteData<typeof config>;
|
|
1009
|
+
|
|
1010
|
+
// Optional narrowing when routes diverge:
|
|
1011
|
+
// export type HomeData = RouteData<typeof config, '/'>;
|
|
1012
|
+
// export type StreamingData = RouteData<typeof config, '/streaming'>;
|
|
1013
|
+
`;
|
|
1014
|
+
}
|
|
882
1015
|
function generateViteEnv() {
|
|
883
1016
|
return `/// <reference types="vite/client" />
|
|
884
1017
|
`;
|
|
@@ -969,13 +1102,11 @@ function generateHomePageVue() {
|
|
|
969
1102
|
return `<script setup lang="ts">
|
|
970
1103
|
import { useSSRData } from '@taujs/vue';
|
|
971
1104
|
|
|
972
|
-
type
|
|
973
|
-
message: string;
|
|
974
|
-
timestamp: string;
|
|
975
|
-
};
|
|
1105
|
+
import type { AppData } from './app-types';
|
|
976
1106
|
|
|
977
1107
|
// Fallback idiom: non-blocking; \`data\` is undefined until ready, guarded with v-if.
|
|
978
|
-
|
|
1108
|
+
// AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
|
|
1109
|
+
const data = useSSRData<AppData>();
|
|
979
1110
|
</script>
|
|
980
1111
|
|
|
981
1112
|
<template>
|
|
@@ -994,13 +1125,11 @@ function generateStreamingPageVue() {
|
|
|
994
1125
|
return `<script setup lang="ts">
|
|
995
1126
|
import { useSSRDataAsync } from '@taujs/vue';
|
|
996
1127
|
|
|
997
|
-
type
|
|
998
|
-
message: string;
|
|
999
|
-
timestamp: string;
|
|
1000
|
-
};
|
|
1128
|
+
import type { AppData } from './app-types';
|
|
1001
1129
|
|
|
1002
1130
|
// Suspense idiom: async setup blocks on the data, so streamed routes deliver it in the payload.
|
|
1003
|
-
|
|
1131
|
+
// AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
|
|
1132
|
+
const data = await useSSRDataAsync<AppData>();
|
|
1004
1133
|
</script>
|
|
1005
1134
|
|
|
1006
1135
|
<template>
|
|
@@ -1028,13 +1157,17 @@ function generateEntryServerVue() {
|
|
|
1028
1157
|
|
|
1029
1158
|
import App from './App.vue';
|
|
1030
1159
|
|
|
1031
|
-
|
|
1160
|
+
import type { AppData, AppRouteContext } from './app-types';
|
|
1161
|
+
|
|
1162
|
+
// Generics derived from taujs.config.ts: typed data for headContent and the store, typed
|
|
1163
|
+
// routeContext for appComponent.
|
|
1164
|
+
export const { renderSSR, renderStream } = createRenderer<AppData, AppRouteContext>({
|
|
1032
1165
|
appComponent: App,
|
|
1033
1166
|
headContent: ({ data, meta }) => \`
|
|
1034
1167
|
<title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
|
|
1035
1168
|
<meta name="description" content="\${
|
|
1036
1169
|
meta?.description ||
|
|
1037
|
-
|
|
1170
|
+
data?.message ||
|
|
1038
1171
|
"\u03C4js - Composing systems, not just apps"
|
|
1039
1172
|
}">
|
|
1040
1173
|
\`,
|
|
@@ -1057,7 +1190,11 @@ function generateEntryServer() {
|
|
|
1057
1190
|
return `import { createRenderer } from '@taujs/react';
|
|
1058
1191
|
import { App } from './App';
|
|
1059
1192
|
|
|
1060
|
-
|
|
1193
|
+
import type { AppData, AppRouteContext } from './app-types';
|
|
1194
|
+
|
|
1195
|
+
// Generics derived from taujs.config.ts: typed data for headContent and the store, typed
|
|
1196
|
+
// routeContext for appComponent.
|
|
1197
|
+
export const { renderSSR, renderStream } = createRenderer<AppData, AppRouteContext>({
|
|
1061
1198
|
appComponent: () => <App />,
|
|
1062
1199
|
headContent: ({ data, meta }) => \`
|
|
1063
1200
|
<title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
|
|
@@ -1071,6 +1208,87 @@ export const { renderSSR, renderStream } = createRenderer({
|
|
|
1071
1208
|
});
|
|
1072
1209
|
`;
|
|
1073
1210
|
}
|
|
1211
|
+
function generateSolidCompilerTsConfig() {
|
|
1212
|
+
return {
|
|
1213
|
+
compilerOptions: {
|
|
1214
|
+
jsx: "preserve",
|
|
1215
|
+
jsxImportSource: "solid-js"
|
|
1216
|
+
},
|
|
1217
|
+
include: ["src/client/**/*.tsx"]
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function generateSolidRenderId() {
|
|
1221
|
+
return `/**
|
|
1222
|
+
* The renderId is a SHARED constant: the server renders Solid's markers and serialised data under
|
|
1223
|
+
* this namespace, and the client must hydrate under the SAME one. It is imported by BOTH entries
|
|
1224
|
+
* on purpose - a literal duplicated in two files is a hydration bug waiting to happen.
|
|
1225
|
+
*/
|
|
1226
|
+
export const RENDER_ID = 'app';
|
|
1227
|
+
`;
|
|
1228
|
+
}
|
|
1229
|
+
function generateAppComponentSolid() {
|
|
1230
|
+
return `import { Show } from 'solid-js';
|
|
1231
|
+
import { useSSRStore } from '@taujs/solid';
|
|
1232
|
+
|
|
1233
|
+
import type { AppData } from './app-types';
|
|
1234
|
+
|
|
1235
|
+
export function App() {
|
|
1236
|
+
// Route data arrives through the store, which the renderer provides. On the server it is already
|
|
1237
|
+
// committed before the render begins; on the client it is seeded from window.__INITIAL_DATA__.
|
|
1238
|
+
// AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
|
|
1239
|
+
const store = useSSRStore<AppData>();
|
|
1240
|
+
|
|
1241
|
+
return (
|
|
1242
|
+
<main>
|
|
1243
|
+
<h1>\u03C4js + Solid</h1>
|
|
1244
|
+
<Show when={store.data().message} fallback={<p>No route data.</p>}>
|
|
1245
|
+
<p>{store.data().message}</p>
|
|
1246
|
+
</Show>
|
|
1247
|
+
</main>
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
`;
|
|
1251
|
+
}
|
|
1252
|
+
function generateEntryClientSolid() {
|
|
1253
|
+
return `import { hydrateApp } from '@taujs/solid';
|
|
1254
|
+
|
|
1255
|
+
import { App } from './App';
|
|
1256
|
+
import { RENDER_ID } from './renderId';
|
|
1257
|
+
|
|
1258
|
+
hydrateApp({
|
|
1259
|
+
app: () => <App />,
|
|
1260
|
+
renderId: RENDER_ID,
|
|
1261
|
+
rootElementId: 'root',
|
|
1262
|
+
onHydrationError: (error) => {
|
|
1263
|
+
console.error('Hydration failed:', error);
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
`;
|
|
1267
|
+
}
|
|
1268
|
+
function generateEntryServerSolid() {
|
|
1269
|
+
return `import { createRenderer } from '@taujs/solid';
|
|
1270
|
+
|
|
1271
|
+
import { App } from './App';
|
|
1272
|
+
import { RENDER_ID } from './renderId';
|
|
1273
|
+
|
|
1274
|
+
import type { AppData, AppRouteContext } from './app-types';
|
|
1275
|
+
|
|
1276
|
+
// Generics derived from taujs.config.ts: typed data for headContent and the store, typed
|
|
1277
|
+
// routeContext for appComponent.
|
|
1278
|
+
export const { renderSSR, renderStream } = createRenderer<AppData, AppRouteContext>({
|
|
1279
|
+
appComponent: () => <App />,
|
|
1280
|
+
renderId: RENDER_ID,
|
|
1281
|
+
headContent: ({ data, meta }) => \`
|
|
1282
|
+
<title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
|
|
1283
|
+
<meta name="description" content="\${
|
|
1284
|
+
meta?.description ||
|
|
1285
|
+
data?.message ||
|
|
1286
|
+
"\u03C4js - Composing systems, not just apps"
|
|
1287
|
+
}">
|
|
1288
|
+
\`,
|
|
1289
|
+
});
|
|
1290
|
+
`;
|
|
1291
|
+
}
|
|
1074
1292
|
function generateServerIndex() {
|
|
1075
1293
|
return `import { createServer } from '@taujs/server';
|
|
1076
1294
|
import config from '../../taujs.config.ts';
|
|
@@ -1104,7 +1322,9 @@ export type ServiceRegistry = typeof serviceRegistry;
|
|
|
1104
1322
|
`;
|
|
1105
1323
|
}
|
|
1106
1324
|
function generateServiceTypesAugmentation() {
|
|
1107
|
-
return `
|
|
1325
|
+
return `import '@taujs/server/config';
|
|
1326
|
+
|
|
1327
|
+
declare module '@taujs/server/config' {
|
|
1108
1328
|
interface ServiceContext {
|
|
1109
1329
|
tenantId?: string;
|
|
1110
1330
|
}
|