create-frootjs 1.0.13 → 1.0.15
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/addons/testing.js +2 -2
- package/dist/addons/testing.js.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/templates/fullstack.d.ts +2 -3
- package/dist/templates/fullstack.d.ts.map +1 -1
- package/dist/templates/fullstack.js +137 -346
- package/dist/templates/fullstack.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +0 -2
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/minimal.js +54 -54
- package/dist/templates/modular.js +261 -261
- package/dist/templates/mvc.js +196 -196
- package/dist/templates/openapi.js +168 -168
- package/dist/templates/streaming.js +176 -176
- package/dist/templates/websocket.js +163 -163
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +31 -89
- package/dist/ui.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fullstack
|
|
3
|
-
*
|
|
4
|
-
* Features Next.js App Router-style file-based routing, nested layouts, Preact SSR, SSG, and Markdown content.
|
|
2
|
+
* Fullstack JSX & Server-Side Rendering (SSR) Template
|
|
3
|
+
* Native JSX components rendered on the server with zero client JavaScript overhead.
|
|
5
4
|
*/
|
|
6
|
-
import { COMMON_GITIGNORE } from './types.js';
|
|
5
|
+
import { COMMON_GITIGNORE, COMMON_TSCONFIG } from './types.js';
|
|
7
6
|
export const fullstackTemplate = {
|
|
8
7
|
id: 'fullstack',
|
|
9
|
-
name: 'Fullstack
|
|
10
|
-
description: '
|
|
8
|
+
name: 'Fullstack',
|
|
9
|
+
description: 'Server-rendered JSX views',
|
|
11
10
|
generate: (projectName) => {
|
|
12
11
|
return {
|
|
13
12
|
'package.json': JSON.stringify({
|
|
@@ -16,64 +15,29 @@ export const fullstackTemplate = {
|
|
|
16
15
|
type: 'module',
|
|
17
16
|
scripts: {
|
|
18
17
|
dev: 'tsx watch src/server.tsx',
|
|
19
|
-
build: 'tsc
|
|
20
|
-
'build:static': 'tsx src/build.ts',
|
|
18
|
+
build: 'tsc',
|
|
21
19
|
start: 'node dist/server.js',
|
|
22
20
|
},
|
|
23
21
|
dependencies: {
|
|
24
|
-
|
|
25
|
-
frootjs: '^1.0.6',
|
|
26
|
-
preact: '^11.1.0',
|
|
27
|
-
'preact-render-to-string': '^6.8.2',
|
|
22
|
+
frootjs: '^1.0.2',
|
|
28
23
|
},
|
|
29
24
|
devDependencies: {
|
|
30
|
-
'@tailwindcss/cli': '^4.0.0',
|
|
31
|
-
tailwindcss: '^4.0.0',
|
|
32
25
|
'@types/node': '^22.10.0',
|
|
33
26
|
tsx: '^4.19.0',
|
|
34
27
|
typescript: '^5.8.0',
|
|
35
28
|
},
|
|
36
29
|
}, null, 2) + '\n',
|
|
37
|
-
'tsconfig.json':
|
|
38
|
-
"compilerOptions": {
|
|
39
|
-
"target": "ES2024",
|
|
40
|
-
"module": "NodeNext",
|
|
41
|
-
"moduleResolution": "NodeNext",
|
|
42
|
-
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
43
|
-
"declaration": true,
|
|
44
|
-
"declarationMap": true,
|
|
45
|
-
"sourceMap": true,
|
|
46
|
-
"outDir": "./dist",
|
|
47
|
-
"rootDir": "./src",
|
|
48
|
-
"strict": true,
|
|
49
|
-
"noImplicitAny": true,
|
|
50
|
-
"strictNullChecks": true,
|
|
51
|
-
"skipLibCheck": true,
|
|
52
|
-
"forceConsistentCasingInFileNames": true,
|
|
53
|
-
"jsx": "react-jsx",
|
|
54
|
-
"jsxImportSource": "preact"
|
|
55
|
-
},
|
|
56
|
-
"include": ["src/**/*"],
|
|
57
|
-
"exclude": ["node_modules", "dist"]
|
|
58
|
-
}
|
|
59
|
-
`,
|
|
30
|
+
'tsconfig.json': COMMON_TSCONFIG,
|
|
60
31
|
'.gitignore': COMMON_GITIGNORE,
|
|
61
32
|
'README.md': `# ${projectName}
|
|
62
33
|
|
|
63
|
-
|
|
34
|
+
Froot application built with the **Fullstack JSX & Server-Side Rendering (SSR)** pattern.
|
|
64
35
|
|
|
65
36
|
## Features
|
|
66
37
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
- 📂 **Next.js App Router Architecture**:
|
|
71
|
-
- File-based routing automatically discovered from \`src/app\`
|
|
72
|
-
- Hierarchical nested layout system (\`app/layout.tsx\` -> \`app/docs/layout.tsx\`)
|
|
73
|
-
- Dynamic route dispatch with bracket parameters (\`app/docs/[slug]/page.tsx\`)
|
|
74
|
-
- Distinct layouts (e.g. landing page with navbar vs documentation with left sidebar and no top navbar)
|
|
75
|
-
- 🚀 **Static Site Generation (SSG)**: Prerender static pages and dynamic routes with \`generateStaticParams()\`.
|
|
76
|
-
- 📝 **Built-in Markdown Engine**: Next.js MDX/Contentlayer alternative with zero dependencies.
|
|
38
|
+
- **Native JSX / TSX**: Pure server-rendered JSX without React runtime baggage
|
|
39
|
+
- **Layout Middleware**: Automatic HTML wrapper layout with \`jsxRenderer()\`
|
|
40
|
+
- **Zero Client JS**: Ultra-fast initial page loads and 100% SEO-friendly markup
|
|
77
41
|
|
|
78
42
|
## Getting Started
|
|
79
43
|
|
|
@@ -81,351 +45,178 @@ High-performance **Next.js alternative** powered by **Froot Framework**, **Preac
|
|
|
81
45
|
# 1. Install dependencies
|
|
82
46
|
pnpm install
|
|
83
47
|
|
|
84
|
-
# 2. Run in development mode
|
|
48
|
+
# 2. Run in development mode
|
|
85
49
|
pnpm run dev
|
|
86
50
|
|
|
87
|
-
# 3.
|
|
88
|
-
pnpm run build:static
|
|
89
|
-
|
|
90
|
-
# 4. View in browser
|
|
51
|
+
# 3. View in browser
|
|
91
52
|
http://localhost:3000
|
|
92
53
|
\`\`\`
|
|
93
54
|
`,
|
|
94
|
-
'
|
|
95
|
-
title: Overview
|
|
96
|
-
category: Getting Started
|
|
97
|
-
summary: Introduction to the Froot Fullstack Next.js Alternative architecture.
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
Welcome to **Froot Fullstack**, the high-performance Next.js alternative powered by Preact, Tailwind CSS v4, and Froot.
|
|
101
|
-
|
|
102
|
-
## Design Principles
|
|
103
|
-
|
|
104
|
-
- **Monomorphic V8 Execution:** Minimal memory overhead and maximum throughput.
|
|
105
|
-
- **File-Based Routing:** Next.js App Router conventions with \`src/app\`, nested layouts, and bracket parameters.
|
|
106
|
-
- **Preact Server-Side Rendering:** Clean JSX syntax with sub-millisecond render times.
|
|
107
|
-
- **Tailwind CSS v4:** Lightning-fast CSS compilation via \`@tailwindcss/cli\` and native \`@theme\` variables.
|
|
108
|
-
- **Layout Isolation:** Dedicated layouts for different application sections (e.g. docs vs marketing).
|
|
109
|
-
`,
|
|
110
|
-
'content/docs/quickstart.md': `---
|
|
111
|
-
title: Quickstart
|
|
112
|
-
category: Getting Started
|
|
113
|
-
summary: Build your first fullstack application in seconds.
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
Create routes, compose Preact components, and serve HTML with extreme performance.
|
|
117
|
-
|
|
118
|
-
## Adding Routes
|
|
119
|
-
|
|
120
|
-
Simply add a new folder or file in \`src/app/\`:
|
|
55
|
+
'src/components/Layout.tsx': `import type { FC, PropsWithChildren } from 'frootjs/jsx';
|
|
121
56
|
|
|
122
|
-
|
|
123
|
-
// src/app/about/page.tsx
|
|
124
|
-
export default function AboutPage() {
|
|
125
|
-
return <h1 class="text-3xl font-bold">About Us</h1>;
|
|
126
|
-
}
|
|
127
|
-
\`\`\`
|
|
128
|
-
`,
|
|
129
|
-
'src/styles.css': `@import "tailwindcss";
|
|
130
|
-
|
|
131
|
-
@theme {
|
|
132
|
-
--color-froot-400: #34d399;
|
|
133
|
-
--color-froot-500: #10b981;
|
|
134
|
-
--color-froot-600: #059669;
|
|
135
|
-
}
|
|
136
|
-
`,
|
|
137
|
-
'src/app/layout.tsx': `import type { ComponentChildren } from 'preact';
|
|
138
|
-
|
|
139
|
-
export interface RootLayoutProps {
|
|
57
|
+
export interface LayoutProps {
|
|
140
58
|
title?: string;
|
|
141
|
-
description?: string;
|
|
142
|
-
children: ComponentChildren;
|
|
143
59
|
}
|
|
144
60
|
|
|
145
|
-
export
|
|
146
|
-
title = 'Froot Fullstack App',
|
|
147
|
-
description = 'High-performance Next.js alternative powered by Froot and Preact',
|
|
148
|
-
children,
|
|
149
|
-
}: RootLayoutProps) {
|
|
61
|
+
export const Layout: FC<PropsWithChildren<LayoutProps>> = ({ title = 'Froot JSX App', children }) => {
|
|
150
62
|
return (
|
|
151
|
-
<html lang="en"
|
|
63
|
+
<html lang="en">
|
|
152
64
|
<head>
|
|
153
65
|
<meta charSet="UTF-8" />
|
|
154
66
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
155
67
|
<title>{title}</title>
|
|
156
|
-
<
|
|
157
|
-
|
|
68
|
+
<style>{\`
|
|
69
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
70
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; line-height: 1.6; }
|
|
71
|
+
header { background: #1e293b; border-bottom: 1px solid #334155; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; }
|
|
72
|
+
.logo { font-size: 1.3rem; font-weight: bold; color: #10b981; text-decoration: none; }
|
|
73
|
+
nav a { color: #94a3b8; text-decoration: none; margin-left: 1.5rem; font-weight: 500; }
|
|
74
|
+
nav a:hover { color: #f8fafc; }
|
|
75
|
+
main { max-width: 900px; margin: 2rem auto; padding: 0 1.5rem; }
|
|
76
|
+
footer { text-align: center; padding: 2rem; color: #64748b; font-size: 0.9rem; border-top: 1px solid #1e293b; margin-top: 4rem; }
|
|
77
|
+
\`}</style>
|
|
158
78
|
</head>
|
|
159
|
-
<body
|
|
160
|
-
|
|
79
|
+
<body>
|
|
80
|
+
<header>
|
|
81
|
+
<a href="/" className="logo">Froot JSX</a>
|
|
82
|
+
<nav>
|
|
83
|
+
<a href="/">Home</a>
|
|
84
|
+
<a href="/about">About</a>
|
|
85
|
+
<a href="/features">Features</a>
|
|
86
|
+
</nav>
|
|
87
|
+
</header>
|
|
88
|
+
<main>{children}</main>
|
|
89
|
+
<footer>
|
|
90
|
+
<p>Powered by Froot Server-Side Rendering Engine</p>
|
|
91
|
+
</footer>
|
|
161
92
|
</body>
|
|
162
93
|
</html>
|
|
163
94
|
);
|
|
164
|
-
}
|
|
165
|
-
`,
|
|
166
|
-
'src/app/page.tsx': `export const metadata = {
|
|
167
|
-
title: 'Froot Fullstack — Next.js Alternative',
|
|
168
|
-
description: 'Extreme performance fullstack web engine with Preact SSR and file-based routing',
|
|
169
95
|
};
|
|
96
|
+
`,
|
|
97
|
+
'src/components/Card.tsx': `import type { FC, PropsWithChildren } from 'frootjs/jsx';
|
|
170
98
|
|
|
171
|
-
export
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<header class="border-b border-slate-800 bg-slate-900/60 backdrop-blur-md sticky top-0 z-50">
|
|
176
|
-
<div class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
|
|
177
|
-
<a href="/" class="flex items-center gap-2 font-bold text-white">
|
|
178
|
-
<span class="text-xl">🍏</span>
|
|
179
|
-
<span>Froot<span class="text-emerald-400">.ts</span></span>
|
|
180
|
-
</a>
|
|
181
|
-
<nav class="flex items-center gap-6 text-sm text-slate-300">
|
|
182
|
-
<a href="#features" class="hover:text-emerald-400 transition-colors">Features</a>
|
|
183
|
-
<a href="/docs" class="text-emerald-400 hover:text-emerald-300 font-semibold">Docs →</a>
|
|
184
|
-
</nav>
|
|
185
|
-
</div>
|
|
186
|
-
</header>
|
|
187
|
-
|
|
188
|
-
{/* Hero Section */}
|
|
189
|
-
<main class="max-w-5xl mx-auto px-6 py-20 text-center">
|
|
190
|
-
<div class="inline-block px-3 py-1 mb-6 text-xs font-semibold text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 rounded-full">
|
|
191
|
-
Next.js Alternative • Preact SSR • Tailwind CSS v4 • File-Based Routing
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
<h1 class="text-5xl font-extrabold tracking-tight text-white mb-6">
|
|
195
|
-
High-Performance <span class="text-emerald-400">Fullstack Web Engine</span>
|
|
196
|
-
</h1>
|
|
197
|
-
|
|
198
|
-
<p class="text-lg text-slate-300 max-w-2xl mx-auto mb-10 leading-relaxed">
|
|
199
|
-
Engineered for extreme throughput, Next.js-style file-based routing, nested layouts, and zero-allocation V8 execution.
|
|
200
|
-
</p>
|
|
201
|
-
|
|
202
|
-
<div class="flex items-center justify-center gap-4">
|
|
203
|
-
<a
|
|
204
|
-
href="/docs"
|
|
205
|
-
class="px-6 py-3 rounded-xl font-semibold bg-emerald-500 text-slate-950 hover:bg-emerald-400 transition-colors shadow-lg shadow-emerald-500/20"
|
|
206
|
-
>
|
|
207
|
-
Explore Documentation
|
|
208
|
-
</a>
|
|
209
|
-
<a
|
|
210
|
-
href="https://github.com/orbitnorth/froot"
|
|
211
|
-
target="_blank"
|
|
212
|
-
rel="noopener noreferrer"
|
|
213
|
-
class="px-6 py-3 rounded-xl font-semibold bg-slate-800 text-slate-200 hover:bg-slate-700 transition-colors border border-slate-700"
|
|
214
|
-
>
|
|
215
|
-
GitHub Repository
|
|
216
|
-
</a>
|
|
217
|
-
</div>
|
|
99
|
+
export interface CardProps {
|
|
100
|
+
title: string;
|
|
101
|
+
badge?: string;
|
|
102
|
+
}
|
|
218
103
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
104
|
+
export const Card: FC<PropsWithChildren<CardProps>> = ({ title, badge, children }) => {
|
|
105
|
+
return (
|
|
106
|
+
<div style={{
|
|
107
|
+
background: '#1e293b',
|
|
108
|
+
border: '1px solid #334155',
|
|
109
|
+
borderRadius: '8px',
|
|
110
|
+
padding: '1.5rem',
|
|
111
|
+
marginBottom: '1.25rem',
|
|
112
|
+
}}>
|
|
113
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
|
|
114
|
+
<h3 style={{ color: '#38bdf8', fontSize: '1.2rem' }}>{title}</h3>
|
|
115
|
+
{badge ? (
|
|
116
|
+
<span style={{
|
|
117
|
+
background: '#064e3b',
|
|
118
|
+
color: '#34d399',
|
|
119
|
+
fontSize: '0.75rem',
|
|
120
|
+
padding: '0.2rem 0.6rem',
|
|
121
|
+
borderRadius: '9999px',
|
|
122
|
+
fontWeight: 600,
|
|
123
|
+
}}>
|
|
124
|
+
{badge}
|
|
125
|
+
</span>
|
|
126
|
+
) : null}
|
|
127
|
+
</div>
|
|
128
|
+
<div>{children}</div>
|
|
238
129
|
</div>
|
|
239
130
|
);
|
|
240
|
-
}
|
|
131
|
+
};
|
|
241
132
|
`,
|
|
242
|
-
'src/
|
|
243
|
-
|
|
244
|
-
export interface DocsLayoutProps {
|
|
245
|
-
currentPath: string;
|
|
246
|
-
children: ComponentChildren;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export default function DocsLayout({ currentPath, children }: DocsLayoutProps) {
|
|
250
|
-
const links = [
|
|
251
|
-
{ href: '/docs', title: 'Overview' },
|
|
252
|
-
{ href: '/docs/quickstart', title: 'Quickstart' },
|
|
253
|
-
];
|
|
133
|
+
'src/pages/Home.tsx': `import type { FC } from 'frootjs/jsx';
|
|
134
|
+
import { Card } from '../components/Card.js';
|
|
254
135
|
|
|
136
|
+
export const HomePage: FC = () => {
|
|
255
137
|
return (
|
|
256
|
-
<div
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
</
|
|
264
|
-
|
|
265
|
-
<div class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">
|
|
266
|
-
Getting Started
|
|
267
|
-
</div>
|
|
268
|
-
|
|
269
|
-
<nav class="space-y-1">
|
|
270
|
-
{links.map((link) => {
|
|
271
|
-
const isActive = currentPath === link.href || (link.href === '/docs' && currentPath === '/docs/overview');
|
|
272
|
-
const cls = isActive
|
|
273
|
-
? 'bg-emerald-500/10 text-emerald-400 font-semibold border-l-2 border-emerald-400 pl-2.5'
|
|
274
|
-
: 'text-slate-400 hover:text-white hover:bg-slate-800/60';
|
|
138
|
+
<div>
|
|
139
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
140
|
+
<h1 style={{ fontSize: '2.25rem', color: '#10b981', marginBottom: '0.5rem' }}>
|
|
141
|
+
Server-Side Rendered JSX
|
|
142
|
+
</h1>
|
|
143
|
+
<p style={{ color: '#94a3b8', fontSize: '1.1rem' }}>
|
|
144
|
+
Blazing fast HTML generated on the server with TypeScript-native JSX components.
|
|
145
|
+
</p>
|
|
146
|
+
</div>
|
|
275
147
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
href={link.href}
|
|
280
|
-
class={\`block px-3 py-2 text-xs rounded-lg transition-colors \${cls}\`}
|
|
281
|
-
>
|
|
282
|
-
{link.title}
|
|
283
|
-
</a>
|
|
284
|
-
);
|
|
285
|
-
})}
|
|
286
|
-
</nav>
|
|
148
|
+
<Card title="Zero Client Runtime" badge="Performance">
|
|
149
|
+
<p>No heavy virtual DOM diffing in the browser. Pure, pre-rendered semantic HTML delivered instantly.</p>
|
|
150
|
+
</Card>
|
|
287
151
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
<span>Back to Home</span>
|
|
292
|
-
</a>
|
|
293
|
-
</div>
|
|
294
|
-
</aside>
|
|
152
|
+
<Card title="Type-Safe Views" badge="DX">
|
|
153
|
+
<p>Full TypeScript compiler checking for props, children, styles, and HTML attributes.</p>
|
|
154
|
+
</Card>
|
|
295
155
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
</main>
|
|
156
|
+
<Card title="Streaming & Islands Ready" badge="Architecture">
|
|
157
|
+
<p>Seamlessly compose asynchronous server components and stream output as it becomes available.</p>
|
|
158
|
+
</Card>
|
|
300
159
|
</div>
|
|
301
160
|
);
|
|
302
|
-
}
|
|
303
|
-
`,
|
|
304
|
-
'src/app/docs/page.tsx': `import * as path from 'node:path';
|
|
305
|
-
import { fileURLToPath } from 'node:url';
|
|
306
|
-
import { renderMarkdownFile } from 'froot-fullstack';
|
|
307
|
-
|
|
308
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
309
|
-
const __dirname = path.dirname(__filename);
|
|
310
|
-
|
|
311
|
-
export const metadata = {
|
|
312
|
-
title: 'Documentation Overview — Froot Fullstack',
|
|
313
|
-
description: 'Overview of the high-performance Froot Fullstack framework.',
|
|
314
161
|
};
|
|
162
|
+
`,
|
|
163
|
+
'src/pages/About.tsx': `import type { FC } from 'frootjs/jsx';
|
|
315
164
|
|
|
316
|
-
export
|
|
317
|
-
const docPath = path.resolve(process.cwd(), 'content/docs/overview.md');
|
|
318
|
-
const { frontmatter, html } = await renderMarkdownFile(docPath);
|
|
319
|
-
|
|
165
|
+
export const AboutPage: FC = () => {
|
|
320
166
|
return (
|
|
321
|
-
<div>
|
|
322
|
-
<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
dangerouslySetInnerHTML={{ __html: html }}
|
|
331
|
-
/>
|
|
167
|
+
<div style={{ background: '#1e293b', padding: '2rem', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
168
|
+
<h1 style={{ color: '#10b981', marginBottom: '1rem' }}>About This Application</h1>
|
|
169
|
+
<p style={{ marginBottom: '1rem' }}>
|
|
170
|
+
This project showcases Froot's built-in JSX rendering pipeline. Unlike traditional React or Next.js setups,
|
|
171
|
+
Froot renders JSX straight to HTML strings without requiring React, ReactDOM, or a Node.js bundling step.
|
|
172
|
+
</p>
|
|
173
|
+
<p style={{ color: '#94a3b8' }}>
|
|
174
|
+
It achieves sub-millisecond page render times while maintaining full component composition.
|
|
175
|
+
</p>
|
|
332
176
|
</div>
|
|
333
177
|
);
|
|
334
|
-
}
|
|
178
|
+
};
|
|
335
179
|
`,
|
|
336
|
-
'src/
|
|
337
|
-
import {
|
|
338
|
-
import {
|
|
180
|
+
'src/server.tsx': `import { Froot } from 'frootjs';
|
|
181
|
+
import { jsxRenderer } from 'frootjs/jsx';
|
|
182
|
+
import { Layout } from './components/Layout.js';
|
|
183
|
+
import { HomePage } from './pages/Home.js';
|
|
184
|
+
import { AboutPage } from './pages/About.js';
|
|
339
185
|
|
|
340
|
-
const
|
|
341
|
-
const __dirname = path.dirname(__filename);
|
|
186
|
+
export const app = new Froot();
|
|
342
187
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
];
|
|
348
|
-
}
|
|
188
|
+
// Wrap all HTML responses in the global Layout shell
|
|
189
|
+
app.use('*', jsxRenderer(({ children }) => {
|
|
190
|
+
return <Layout>{children}</Layout>;
|
|
191
|
+
}));
|
|
349
192
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return
|
|
353
|
-
|
|
354
|
-
};
|
|
355
|
-
}
|
|
193
|
+
// Route handlers
|
|
194
|
+
app.get('/', (c) => {
|
|
195
|
+
return c.render(<HomePage />);
|
|
196
|
+
});
|
|
356
197
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const { frontmatter, html } = await renderMarkdownFile(docPath);
|
|
198
|
+
app.get('/about', (c) => {
|
|
199
|
+
return c.render(<AboutPage />);
|
|
200
|
+
});
|
|
361
201
|
|
|
362
|
-
|
|
202
|
+
app.get('/features', (c) => {
|
|
203
|
+
return c.render(
|
|
363
204
|
<div>
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
dangerouslySetInnerHTML={{ __html: html }}
|
|
373
|
-
/>
|
|
205
|
+
<h1 style={{ color: '#10b981', marginBottom: '1rem' }}>Framework Features</h1>
|
|
206
|
+
<ul style={{ paddingLeft: '1.5rem', color: '#cbd5e1' }}>
|
|
207
|
+
<li>RADIX tree router with O(K) lookup</li>
|
|
208
|
+
<li>Auto-generated OpenAPI 3.1 & Scalar docs</li>
|
|
209
|
+
<li>Native JSX & async component support</li>
|
|
210
|
+
<li>Full WebCrypto authentication ecosystem</li>
|
|
211
|
+
<li>Universal WebSockets & pub/sub hub</li>
|
|
212
|
+
</ul>
|
|
374
213
|
</div>
|
|
375
214
|
);
|
|
376
|
-
}
|
|
377
|
-
`,
|
|
378
|
-
'src/server.tsx': `import * as path from 'node:path';
|
|
379
|
-
import { fileURLToPath } from 'node:url';
|
|
380
|
-
import { Froot } from 'frootjs';
|
|
381
|
-
import { createAppRouter } from 'froot-fullstack';
|
|
382
|
-
|
|
383
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
384
|
-
const __dirname = path.dirname(__filename);
|
|
385
|
-
|
|
386
|
-
export const app = new Froot();
|
|
387
|
-
export const router = createAppRouter(app);
|
|
388
|
-
|
|
389
|
-
// 1. Enable built-in Tailwind CSS v4 pipeline (watches in dev, serves /styles.css)
|
|
390
|
-
await router.enableTailwind({
|
|
391
|
-
input: path.resolve(__dirname, 'styles.css'),
|
|
392
|
-
output: path.resolve(__dirname, '../dist/styles.css'),
|
|
393
215
|
});
|
|
394
216
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const PORT = Number(process.env['PORT'] || 3000);
|
|
400
|
-
const isDirectEntry = process.argv[1] ? path.basename(process.argv[1]).includes('server') : false;
|
|
401
|
-
if (isDirectEntry && process.env['NODE_ENV'] !== 'test') {
|
|
402
|
-
app.listen(PORT, () => {
|
|
403
|
-
console.log(\`🍏 Froot Fullstack server running at http://localhost:\${PORT}\`);
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
`,
|
|
407
|
-
'src/build.ts': `/**
|
|
408
|
-
* Static Site Generation (SSG) Builder
|
|
409
|
-
*/
|
|
410
|
-
process.env['FROOT_BUILD'] = 'true';
|
|
411
|
-
|
|
412
|
-
import * as path from 'node:path';
|
|
413
|
-
import { fileURLToPath } from 'node:url';
|
|
414
|
-
import { router } from './server.js';
|
|
415
|
-
|
|
416
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
417
|
-
const __dirname = path.dirname(__filename);
|
|
418
|
-
const outDir = path.resolve(__dirname, '../dist/static');
|
|
419
|
-
|
|
420
|
-
async function build() {
|
|
421
|
-
console.log('🍏 Building static site with froot-fullstack...');
|
|
422
|
-
const result = await router.exportStatic({ outDir });
|
|
423
|
-
console.log(\`✅ Export complete in \${result.durationMs}ms: \${result.renderedRoutes.length} routes pre-rendered.\`);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
build().catch((err) => {
|
|
427
|
-
console.error('❌ Build failed:', err);
|
|
428
|
-
process.exit(1);
|
|
217
|
+
const PORT = Number(process.env.PORT) || 3000;
|
|
218
|
+
app.listen(PORT, () => {
|
|
219
|
+
console.log(\`Fullstack JSX server running on http://localhost:\${PORT}\`);
|
|
429
220
|
});
|
|
430
221
|
`,
|
|
431
222
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fullstack.js","sourceRoot":"","sources":["../../src/templates/fullstack.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"fullstack.js","sourceRoot":"","sources":["../../src/templates/fullstack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuC,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG,MAAM,CAAC,MAAM,iBAAiB,GAAuB;IACnD,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,2BAA2B;IACxC,QAAQ,EAAE,CAAC,WAAmB,EAAmB,EAAE;QACjD,OAAO;YACL,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;gBACE,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,OAAO;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE;oBACP,GAAG,EAAE,0BAA0B;oBAC/B,KAAK,EAAE,KAAK;oBACZ,KAAK,EAAE,qBAAqB;iBAC7B;gBACD,YAAY,EAAE;oBACZ,OAAO,EAAE,QAAQ;iBAClB;gBACD,eAAe,EAAE;oBACf,aAAa,EAAE,UAAU;oBACzB,GAAG,EAAE,SAAS;oBACd,UAAU,EAAE,QAAQ;iBACrB;aACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI;YAER,eAAe,EAAE,eAAe;YAChC,YAAY,EAAE,gBAAgB;YAE9B,WAAW,EAAE,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;CAsBlC;YAEK,2BAA2B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyClC;YAEK,yBAAyB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChC;YAEK,oBAAoB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B3B;YAEK,qBAAqB,EAAE;;;;;;;;;;;;;;;;CAgB5B;YAEK,gBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCvB;SACI,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAQhD,cAAc,YAAY,CAAC;AAE3B,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAOxD,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,kBAAkB,EAA6B,CAAC;AAE5E,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEtE"}
|
package/dist/templates/index.js
CHANGED
|
@@ -7,7 +7,6 @@ import { modularTemplate } from './modular.js';
|
|
|
7
7
|
import { streamingTemplate } from './streaming.js';
|
|
8
8
|
import { websocketTemplate } from './websocket.js';
|
|
9
9
|
import { openapiTemplate } from './openapi.js';
|
|
10
|
-
import { fullstackTemplate } from './fullstack.js';
|
|
11
10
|
import { minimalTemplate } from './minimal.js';
|
|
12
11
|
export * from './types.js';
|
|
13
12
|
export const TEMPLATES = {
|
|
@@ -16,7 +15,6 @@ export const TEMPLATES = {
|
|
|
16
15
|
streaming: streamingTemplate,
|
|
17
16
|
websocket: websocketTemplate,
|
|
18
17
|
openapi: openapiTemplate,
|
|
19
|
-
fullstack: fullstackTemplate,
|
|
20
18
|
minimal: minimalTemplate,
|
|
21
19
|
};
|
|
22
20
|
export const TEMPLATE_LIST = Object.values(TEMPLATES);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc,YAAY,CAAC;AAE3B,MAAM,CAAC,MAAM,SAAS,GAAuC;IAC3D,GAAG,EAAE,WAAW;IAChB,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;IAC5B,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,eAAe;CACzB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAyB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAE5E,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACrC,CAAC"}
|