@punkestu/react-html 0.1.0 → 0.1.1
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/cli/docs-content.d.ts +8 -0
- package/dist/cli/docs-content.d.ts.map +1 -0
- package/dist/cli/docs-content.js +261 -0
- package/dist/cli/docs-content.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +113 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-docs.d.ts +5 -0
- package/dist/cli/tui-docs.d.ts.map +1 -0
- package/dist/cli/tui-docs.js +191 -0
- package/dist/cli/tui-docs.js.map +1 -0
- package/dist/client/hydrate.d.ts +4 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/hydrate.js +80 -0
- package/dist/client/hydrate.js.map +1 -0
- package/dist/core/bundler.d.ts +7 -0
- package/dist/core/bundler.d.ts.map +1 -0
- package/dist/core/bundler.js +103 -0
- package/dist/core/bundler.js.map +1 -0
- package/dist/core/formatter.d.ts +6 -0
- package/dist/core/formatter.d.ts.map +1 -0
- package/dist/core/formatter.js +74 -0
- package/dist/core/formatter.js.map +1 -0
- package/dist/core/island-client.d.ts +9 -0
- package/dist/core/island-client.d.ts.map +1 -0
- package/dist/core/island-client.js +8 -0
- package/dist/core/island-client.js.map +1 -0
- package/dist/core/island-registry.d.ts +15 -0
- package/dist/core/island-registry.d.ts.map +1 -0
- package/dist/core/island-registry.js +17 -0
- package/dist/core/island-registry.js.map +1 -0
- package/dist/core/island.d.ts +32 -0
- package/dist/core/island.d.ts.map +1 -0
- package/dist/core/island.js +47 -0
- package/dist/core/island.js.map +1 -0
- package/dist/core/loader.d.ts +11 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +73 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/renderer.d.ts +20 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/renderer.js +53 -0
- package/dist/core/renderer.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/server/dev-server.d.ts +8 -0
- package/dist/server/dev-server.d.ts.map +1 -0
- package/dist/server/dev-server.js +112 -0
- package/dist/server/dev-server.js.map +1 -0
- package/dist/server/engine.d.ts +46 -0
- package/dist/server/engine.d.ts.map +1 -0
- package/dist/server/engine.js +222 -0
- package/dist/server/engine.js.map +1 -0
- package/{src/server/index.ts → dist/server/index.d.ts} +1 -1
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +4 -1
- package/.react-html/cache/page-fefe905ae5d2.mjs +0 -9
- package/DOCS.md +0 -317
- package/example/public/styles.css +0 -184
- package/example/src/components/Card.tsx +0 -17
- package/example/src/components/Counter.island.tsx +0 -35
- package/example/src/components/Footer.tsx +0 -13
- package/example/src/components/Header.tsx +0 -20
- package/example/src/components/Layout.tsx +0 -39
- package/example/src/components/Tabs.island.tsx +0 -59
- package/example/src/pages/index.tsx +0 -49
- package/example/src/pages/pure.tsx +0 -56
- package/src/cli/docs-content.ts +0 -268
- package/src/cli/index.ts +0 -113
- package/src/cli/tui-docs.ts +0 -214
- package/src/client/hydrate.ts +0 -84
- package/src/core/bundler.ts +0 -122
- package/src/core/formatter.ts +0 -92
- package/src/core/island-client.ts +0 -23
- package/src/core/island-registry.ts +0 -35
- package/src/core/island.tsx +0 -110
- package/src/core/loader.ts +0 -89
- package/src/core/renderer.ts +0 -80
- package/src/index.ts +0 -138
- package/src/server/dev-server.ts +0 -125
- package/src/server/engine.ts +0 -270
- package/tests/engine.test.mjs +0 -288
- package/tsconfig.json +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@punkestu/react-html",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Engine to convert React/JSX to pure HTML with selective reactive island compilation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
11
14
|
"exports": {
|
|
12
15
|
".": {
|
|
13
16
|
"import": "./dist/index.js",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// .server-test-fixture/src/pages/index.tsx
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
function Home() {
|
|
4
|
-
return /* @__PURE__ */ jsx("h1", { children: "UPDATED LIVE IN DEV" });
|
|
5
|
-
}
|
|
6
|
-
export {
|
|
7
|
-
Home as default
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLnNlcnZlci10ZXN0LWZpeHR1cmUvc3JjL3BhZ2VzL2luZGV4LnRzeCJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiXG4gICAgICAgIGltcG9ydCBSZWFjdCBmcm9tICdyZWFjdCc7XG4gICAgICAgIGV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIEhvbWUoKSB7XG4gICAgICAgICAgcmV0dXJuIDxoMT5VUERBVEVEIExJVkUgSU4gREVWPC9oMT47XG4gICAgICAgIH1cbiAgICAgICAgIl0sCiAgIm1hcHBpbmdzIjogIjtBQUdpQjtBQURNLFNBQVIsT0FBd0I7QUFDN0IsU0FBTyxvQkFBQyxRQUFHLGlDQUFtQjtBQUNoQzsiLAogICJuYW1lcyI6IFtdCn0K
|
package/DOCS.md
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
# 📖 react-html Complete Manual & Documentation
|
|
2
|
-
|
|
3
|
-
A high-performance static engine to compile React/JSX to **pure, semantic HTML** with selective reactive compilation for interactive components via an **Islands Architecture**.
|
|
4
|
-
|
|
5
|
-
> 💡 **Terminal UI Access**: You can also browse this documentation interactively in your terminal by running:
|
|
6
|
-
> ```bash
|
|
7
|
-
> npx react-html docs
|
|
8
|
-
> # Or print a specific section:
|
|
9
|
-
> npx react-html docs islands
|
|
10
|
-
> ```
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Table of Contents
|
|
15
|
-
|
|
16
|
-
1. [Overview & Philosophy](#1-overview--philosophy)
|
|
17
|
-
2. [Getting Started & Setup](#2-getting-started--setup)
|
|
18
|
-
3. [Modular HTML with React](#3-modular-html-with-react)
|
|
19
|
-
4. [Reactive Islands Architecture](#4-reactive-islands-architecture)
|
|
20
|
-
5. [Hydration Directives](#5-hydration-directives)
|
|
21
|
-
6. [CLI Commands & Options](#6-cli-commands--options)
|
|
22
|
-
7. [Programmatic API Reference](#7-programmatic-api-reference)
|
|
23
|
-
8. [Server App Integration (Express, Fastify, Node HTTP)](#8-server-app-integration-express-fastify-node-http)
|
|
24
|
-
9. [Best Practices & Troubleshooting](#9-best-practices--troubleshooting)
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 1. Overview & Philosophy
|
|
29
|
-
|
|
30
|
-
Modern web development often forces a painful choice:
|
|
31
|
-
- **Write raw HTML/handlebars/nunjucks**: You lose type safety, component modularity, and rich JSX composition.
|
|
32
|
-
- **Use modern React frameworks (Next.js, CRA, Remix)**: You inadvertently ship massive megabytes of JavaScript, client routers, and heavy hydration runtimes even for mostly static pages.
|
|
33
|
-
|
|
34
|
-
**`react-html` gives you the best of both worlds**:
|
|
35
|
-
1. **Zero JavaScript by Default**:
|
|
36
|
-
Pages without interactive islands output 100% pure, clean HTML5. No client-side React runtime, no `data-reactroot` attributes, and 0 KB JavaScript sent to visitors.
|
|
37
|
-
2. **Modular & DRY Components**:
|
|
38
|
-
Compose your pages with `<Layout>`, `<Header>`, `<Card>`, `<Footer>`, loops, and conditionals using standard React and TypeScript.
|
|
39
|
-
3. **Selective Reactivity**:
|
|
40
|
-
Interactive components (counters, carousels, modals, filters) are marked as **Islands**. The engine pre-renders their initial markup for instant paint and bundles only that component into a standalone client script using `esbuild`.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## 2. Getting Started & Setup
|
|
45
|
-
|
|
46
|
-
### Prerequisites
|
|
47
|
-
- Standard **Node.js v20.0.0 or higher** with `npm` (no Bun dependencies).
|
|
48
|
-
|
|
49
|
-
### Installation
|
|
50
|
-
```bash
|
|
51
|
-
npm install react-html
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Or clone and link locally:
|
|
55
|
-
```bash
|
|
56
|
-
git clone https://github.com/microapes/react-html.git
|
|
57
|
-
cd react-html
|
|
58
|
-
npm install
|
|
59
|
-
npm run build
|
|
60
|
-
npm link
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Recommended Directory Structure
|
|
64
|
-
|
|
65
|
-
```text
|
|
66
|
-
my-project/
|
|
67
|
-
├── package.json
|
|
68
|
-
├── public/ # Static files copied to dist/ verbatim
|
|
69
|
-
│ ├── favicon.ico
|
|
70
|
-
│ └── styles.css
|
|
71
|
-
└── src/
|
|
72
|
-
├── components/ # Reusable modular UI components
|
|
73
|
-
│ ├── Layout.tsx # Master HTML wrapper (<head>, <body>)
|
|
74
|
-
│ ├── Header.tsx # Navigation header
|
|
75
|
-
│ ├── Footer.tsx # Footer component
|
|
76
|
-
│ └── Counter.island.tsx # Interactive island component
|
|
77
|
-
└── pages/ # Page entrypoints
|
|
78
|
-
├── index.tsx # -> dist/index.html
|
|
79
|
-
├── about.tsx # -> dist/about.html
|
|
80
|
-
└── docs/
|
|
81
|
-
└── guide.tsx # -> dist/docs/guide.html
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## 3. Modular HTML with React
|
|
87
|
-
|
|
88
|
-
Eliminate repetitive HTML across your project by creating reusable layout components:
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
// src/components/Layout.tsx
|
|
92
|
-
import React, { ReactNode } from 'react';
|
|
93
|
-
import { Header } from './Header.js';
|
|
94
|
-
import { Footer } from './Footer.js';
|
|
95
|
-
|
|
96
|
-
export interface LayoutProps {
|
|
97
|
-
title?: string;
|
|
98
|
-
description?: string;
|
|
99
|
-
children: ReactNode;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function Layout({
|
|
103
|
-
title = 'Default Title',
|
|
104
|
-
description = 'My static site',
|
|
105
|
-
children
|
|
106
|
-
}: LayoutProps) {
|
|
107
|
-
return (
|
|
108
|
-
<html lang="en">
|
|
109
|
-
<head>
|
|
110
|
-
<meta charSet="UTF-8" />
|
|
111
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
112
|
-
<title>{title}</title>
|
|
113
|
-
<meta name="description" content={description} />
|
|
114
|
-
<link rel="stylesheet" href="/styles.css" />
|
|
115
|
-
</head>
|
|
116
|
-
<body>
|
|
117
|
-
<Header />
|
|
118
|
-
<main className="container">{children}</main>
|
|
119
|
-
<Footer />
|
|
120
|
-
</body>
|
|
121
|
-
</html>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Now, every page simply wraps its content without repeating `<head>`, stylesheets, or `<header>`/`<footer>`:
|
|
127
|
-
|
|
128
|
-
```tsx
|
|
129
|
-
// src/pages/about.tsx
|
|
130
|
-
import React from 'react';
|
|
131
|
-
import { Layout } from '../components/Layout.js';
|
|
132
|
-
|
|
133
|
-
export const title = 'About Us';
|
|
134
|
-
|
|
135
|
-
export default function AboutPage() {
|
|
136
|
-
return (
|
|
137
|
-
<Layout title={title}>
|
|
138
|
-
<h1>About Our Project</h1>
|
|
139
|
-
<p>This entire page compiles into pure static HTML with 0 KB JavaScript!</p>
|
|
140
|
-
</Layout>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## 4. Reactive Islands Architecture
|
|
148
|
-
|
|
149
|
-
When a component needs client-side state (`useState`, `useEffect`, event listeners):
|
|
150
|
-
|
|
151
|
-
### Step 1: Wrap Component with `island()`
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
// src/components/Counter.island.tsx
|
|
155
|
-
import React, { useState } from 'react';
|
|
156
|
-
import { island } from 'react-html';
|
|
157
|
-
|
|
158
|
-
export interface CounterProps {
|
|
159
|
-
initialCount?: number;
|
|
160
|
-
step?: number;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function CounterComponent({ initialCount = 0, step = 1 }: CounterProps) {
|
|
164
|
-
const [count, setCount] = useState(initialCount);
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<div className="counter">
|
|
168
|
-
<p>Current: {count}</p>
|
|
169
|
-
<button onClick={() => setCount(c => c - step)}>- {step}</button>
|
|
170
|
-
<button onClick={() => setCount(c => c + step)}>+ {step}</button>
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Wrap with island helper providing Component, source path, and identifier
|
|
176
|
-
export const Counter = island(CounterComponent, import.meta.url, 'Counter');
|
|
177
|
-
export default Counter;
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Step 2: Use in Any Page
|
|
181
|
-
|
|
182
|
-
```tsx
|
|
183
|
-
// src/pages/index.tsx
|
|
184
|
-
import React from 'react';
|
|
185
|
-
import { Layout } from '../components/Layout.js';
|
|
186
|
-
import { Counter } from '../components/Counter.island.js';
|
|
187
|
-
|
|
188
|
-
export default function HomePage() {
|
|
189
|
-
return (
|
|
190
|
-
<Layout title="Home">
|
|
191
|
-
<h1>Interactive Page</h1>
|
|
192
|
-
{/* Pre-rendered to HTML for instant paint, then hydrated with tiny JS */}
|
|
193
|
-
<Counter initialCount={10} step={2} />
|
|
194
|
-
</Layout>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### What Happens Behind the Scenes:
|
|
200
|
-
1. **Server Phase**: The island is pre-rendered with `renderToString` and placed inside a container `<div data-island="Counter" data-props="...">`.
|
|
201
|
-
2. **Bundler Phase**: `esbuild` extracts and bundles only `CounterComponent` into `dist/assets/islands/Counter.js`.
|
|
202
|
-
3. **Injection Phase**: `<script type="module" src="/assets/islands/Counter.js"></script>` is injected right before `</body>`.
|
|
203
|
-
4. **Client Hydration**: The script queries `[data-island="Counter"]` and attaches listeners using React 19's `hydrateRoot`.
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## 5. Hydration Directives
|
|
208
|
-
|
|
209
|
-
Fine-tune client performance using the `client` prop:
|
|
210
|
-
|
|
211
|
-
| Directive | Code Example | When It Hydrates |
|
|
212
|
-
| :--- | :--- | :--- |
|
|
213
|
-
| `load` *(default)* | `<Counter client="load" />` | Immediately upon `DOMContentLoaded`. Ideal for primary UI. |
|
|
214
|
-
| `idle` | `<Counter client="idle" />` | During browser idle time via `requestIdleCallback`. Ideal for background widgets. |
|
|
215
|
-
| `visible` | `<Counter client="visible" />` | When scrolled into view via `IntersectionObserver`. Ideal for below-the-fold content. |
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## 6. CLI Commands & Options
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
# Build static site to dist/
|
|
223
|
-
react-html build
|
|
224
|
-
|
|
225
|
-
# Customize directories
|
|
226
|
-
react-html build --pages src/pages --out dist --root .
|
|
227
|
-
|
|
228
|
-
# Start local preview server with auto-rebuild on file changes
|
|
229
|
-
react-html dev --port 3000 --host localhost
|
|
230
|
-
|
|
231
|
-
# Terminal UI documentation viewer
|
|
232
|
-
react-html docs
|
|
233
|
-
react-html docs --all
|
|
234
|
-
react-html docs islands
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## 7. Programmatic API Reference
|
|
240
|
-
|
|
241
|
-
You can import and trigger `react-html` within custom build scripts or Node.js services:
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import { build, renderPageToHtml, createDevServer } from 'react-html';
|
|
245
|
-
|
|
246
|
-
// 1. Run static build
|
|
247
|
-
const result = await build({
|
|
248
|
-
root: process.cwd(),
|
|
249
|
-
pagesDir: 'src/pages',
|
|
250
|
-
outDir: 'dist',
|
|
251
|
-
minify: true
|
|
252
|
-
});
|
|
253
|
-
console.log(`Generated ${result.pages.length} pages.`);
|
|
254
|
-
|
|
255
|
-
// 2. Programmatic dev server
|
|
256
|
-
const server = await createDevServer({
|
|
257
|
-
port: 8080,
|
|
258
|
-
host: '127.0.0.1'
|
|
259
|
-
});
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## 8. Server App Integration (Express, Fastify, Node HTTP)
|
|
265
|
-
|
|
266
|
-
You can import and use `react-html` directly inside your backend server application:
|
|
267
|
-
|
|
268
|
-
```ts
|
|
269
|
-
import express from 'express';
|
|
270
|
-
import { createEngine } from 'react-html';
|
|
271
|
-
// or: import { createEngine } from 'react-html/server';
|
|
272
|
-
|
|
273
|
-
const app = express();
|
|
274
|
-
|
|
275
|
-
const engine = createEngine({
|
|
276
|
-
pagesDir: 'src/pages',
|
|
277
|
-
distDir: 'dist',
|
|
278
|
-
// Automatically detects process.env.NODE_ENV === 'production'
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// 1. Connect / Express Middleware
|
|
282
|
-
// In Production: finds and serves pre-built static HTML from dist/
|
|
283
|
-
// In Development: dynamically rebuilds TSX on the fly and returns the fresh HTML string!
|
|
284
|
-
app.use(engine.middleware);
|
|
285
|
-
|
|
286
|
-
// 2. Programmatic Route Rendering
|
|
287
|
-
app.get('/dashboard', async (req, res) => {
|
|
288
|
-
const html = await engine.render('dashboard', { user: req.user });
|
|
289
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
290
|
-
res.send(html);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// 3. Native Node HTTP Server
|
|
294
|
-
import http from 'node:http';
|
|
295
|
-
const server = http.createServer(engine.handle);
|
|
296
|
-
server.listen(3000);
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Environment Behavior:
|
|
300
|
-
- **`process.env.NODE_ENV === 'production'`**:
|
|
301
|
-
- Automatically searches for pre-built `.html` files in `distDir` (`dist/index.html`, `dist/about.html`).
|
|
302
|
-
- Streams them immediately to clients with 0 rebuild overhead.
|
|
303
|
-
- Serves static assets (`dist/assets/islands/*.js`, `dist/styles.css`).
|
|
304
|
-
- **`process.env.NODE_ENV !== 'production'` (Development)**:
|
|
305
|
-
- Dynamically rebuilds the corresponding `.tsx` page on each request.
|
|
306
|
-
- Bundles any reactive islands on-the-fly.
|
|
307
|
-
- Returns the fresh HTML string directly in the response—code edits in `src/pages` or `src/components` are reflected instantly on reload without server restarts!
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
## 9. Best Practices & Troubleshooting
|
|
312
|
-
|
|
313
|
-
### Preventing Hydration Mismatches (React Error #418)
|
|
314
|
-
- **Do not read browser globals during initial render**: Avoid `window.innerWidth` or `localStorage.getItem()` directly in the component body. Read them inside a `useEffect()`.
|
|
315
|
-
- **Props consistency**: Ensure initial props passed from the page match what the component expects.
|
|
316
|
-
- **Protected formatting**: `react-html` automatically preserves the exact inner markup of islands so whitespace insertion does not invalidate React's DOM reconciliation.
|
|
317
|
-
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--bg-color: #0f172a;
|
|
3
|
-
--card-bg: #1e293b;
|
|
4
|
-
--text-main: #f8fafc;
|
|
5
|
-
--text-muted: #94a3b8;
|
|
6
|
-
--primary: #38bdf8;
|
|
7
|
-
--primary-hover: #0ea5e9;
|
|
8
|
-
--border: #334155;
|
|
9
|
-
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
* {
|
|
13
|
-
box-sizing: border-box;
|
|
14
|
-
margin: 0;
|
|
15
|
-
padding: 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
body {
|
|
19
|
-
background-color: var(--bg-color);
|
|
20
|
-
color: var(--text-main);
|
|
21
|
-
font-family: var(--font);
|
|
22
|
-
line-height: 1.6;
|
|
23
|
-
min-height: 100vh;
|
|
24
|
-
display: flex;
|
|
25
|
-
flex-direction: column;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.container {
|
|
29
|
-
max-width: 900px;
|
|
30
|
-
margin: 0 auto;
|
|
31
|
-
padding: 0 1.5rem;
|
|
32
|
-
width: 100%;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
header {
|
|
36
|
-
border-bottom: 1px solid var(--border);
|
|
37
|
-
padding: 1.25rem 0;
|
|
38
|
-
background: rgba(15, 23, 42, 0.8);
|
|
39
|
-
backdrop-filter: blur(8px);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
header .header-inner {
|
|
43
|
-
display: flex;
|
|
44
|
-
justify-content: space-between;
|
|
45
|
-
align-items: center;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.logo {
|
|
49
|
-
font-size: 1.25rem;
|
|
50
|
-
font-weight: 700;
|
|
51
|
-
color: var(--primary);
|
|
52
|
-
text-decoration: none;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
nav a {
|
|
56
|
-
color: var(--text-muted);
|
|
57
|
-
text-decoration: none;
|
|
58
|
-
margin-left: 1.5rem;
|
|
59
|
-
transition: color 0.2s;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
nav a:hover, nav a.active {
|
|
63
|
-
color: var(--text-main);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
main {
|
|
67
|
-
flex: 1;
|
|
68
|
-
padding: 3rem 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
h1 {
|
|
72
|
-
font-size: 2.25rem;
|
|
73
|
-
margin-bottom: 1rem;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
p.subtitle {
|
|
77
|
-
color: var(--text-muted);
|
|
78
|
-
font-size: 1.15rem;
|
|
79
|
-
margin-bottom: 2rem;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.grid {
|
|
83
|
-
display: grid;
|
|
84
|
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
85
|
-
gap: 1.5rem;
|
|
86
|
-
margin-bottom: 2.5rem;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.card {
|
|
90
|
-
background: var(--card-bg);
|
|
91
|
-
border: 1px solid var(--border);
|
|
92
|
-
border-radius: 0.75rem;
|
|
93
|
-
padding: 1.5rem;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.card h3 {
|
|
97
|
-
margin-bottom: 0.5rem;
|
|
98
|
-
color: var(--primary);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.card p {
|
|
102
|
-
color: var(--text-muted);
|
|
103
|
-
font-size: 0.95rem;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* Island interactive counter styling */
|
|
107
|
-
.counter-box {
|
|
108
|
-
background: var(--card-bg);
|
|
109
|
-
border: 1px solid var(--border);
|
|
110
|
-
border-radius: 0.75rem;
|
|
111
|
-
padding: 2rem;
|
|
112
|
-
text-align: center;
|
|
113
|
-
margin: 1.5rem 0;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.counter-value {
|
|
117
|
-
font-size: 3rem;
|
|
118
|
-
font-weight: 700;
|
|
119
|
-
color: var(--primary);
|
|
120
|
-
margin: 1rem 0;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.btn-group {
|
|
124
|
-
display: flex;
|
|
125
|
-
justify-content: center;
|
|
126
|
-
gap: 1rem;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
button {
|
|
130
|
-
background: var(--primary);
|
|
131
|
-
color: #0f172a;
|
|
132
|
-
border: none;
|
|
133
|
-
border-radius: 0.5rem;
|
|
134
|
-
padding: 0.6rem 1.25rem;
|
|
135
|
-
font-weight: 600;
|
|
136
|
-
cursor: pointer;
|
|
137
|
-
transition: background 0.2s;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
button:hover {
|
|
141
|
-
background: var(--primary-hover);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* Tabs Island */
|
|
145
|
-
.tabs-container {
|
|
146
|
-
background: var(--card-bg);
|
|
147
|
-
border: 1px solid var(--border);
|
|
148
|
-
border-radius: 0.75rem;
|
|
149
|
-
overflow: hidden;
|
|
150
|
-
margin: 1.5rem 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.tabs-header {
|
|
154
|
-
display: flex;
|
|
155
|
-
background: #0b1120;
|
|
156
|
-
border-bottom: 1px solid var(--border);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.tab-btn {
|
|
160
|
-
background: transparent;
|
|
161
|
-
color: var(--text-muted);
|
|
162
|
-
padding: 0.75rem 1.5rem;
|
|
163
|
-
border-radius: 0;
|
|
164
|
-
border-bottom: 2px solid transparent;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.tab-btn.active {
|
|
168
|
-
color: var(--primary);
|
|
169
|
-
border-bottom: 2px solid var(--primary);
|
|
170
|
-
background: rgba(56, 189, 248, 0.05);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.tab-content {
|
|
174
|
-
padding: 1.5rem;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
footer {
|
|
178
|
-
border-top: 1px solid var(--border);
|
|
179
|
-
padding: 2rem 0;
|
|
180
|
-
text-align: center;
|
|
181
|
-
color: var(--text-muted);
|
|
182
|
-
font-size: 0.875rem;
|
|
183
|
-
}
|
|
184
|
-
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import React, { ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface CardProps {
|
|
4
|
-
title: string;
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
icon?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Card({ title, children, icon }: CardProps) {
|
|
10
|
-
return (
|
|
11
|
-
<div className="card">
|
|
12
|
-
<h3>{icon ? `${icon} ` : ''}{title}</h3>
|
|
13
|
-
<div>{children}</div>
|
|
14
|
-
</div>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { island } from '../../../src/index.js';
|
|
3
|
-
|
|
4
|
-
export interface CounterProps {
|
|
5
|
-
initialCount?: number;
|
|
6
|
-
step?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function CounterComponent({ initialCount = 0, step = 1 }: CounterProps) {
|
|
10
|
-
const [count, setCount] = useState(initialCount);
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div className="counter-box">
|
|
14
|
-
<h3>⚡ Interactive Counter Island</h3>
|
|
15
|
-
<p style={{ color: '#94a3b8', fontSize: '0.9rem' }}>
|
|
16
|
-
This component is rendered statically on the server, then hydrated with tiny JavaScript.
|
|
17
|
-
</p>
|
|
18
|
-
<div className="counter-value">{count}</div>
|
|
19
|
-
<div className="btn-group">
|
|
20
|
-
<button type="button" onClick={() => setCount((c) => c - step)}>
|
|
21
|
-
- {step}
|
|
22
|
-
</button>
|
|
23
|
-
<button type="button" onClick={() => setCount(0)}>
|
|
24
|
-
Reset
|
|
25
|
-
</button>
|
|
26
|
-
<button type="button" onClick={() => setCount((c) => c + step)}>
|
|
27
|
-
+ {step}
|
|
28
|
-
</button>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const Counter = island(CounterComponent, import.meta.url, 'Counter');
|
|
35
|
-
export default Counter;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
export function Footer() {
|
|
4
|
-
const currentYear = new Date().getFullYear();
|
|
5
|
-
return (
|
|
6
|
-
<footer>
|
|
7
|
-
<div className="container">
|
|
8
|
-
<p>© {currentYear} react-html. Modular pure HTML engine with selective reactive islands.</p>
|
|
9
|
-
</div>
|
|
10
|
-
</footer>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
export interface HeaderProps {
|
|
4
|
-
currentPath?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function Header({ currentPath = '/' }: HeaderProps) {
|
|
8
|
-
return (
|
|
9
|
-
<header>
|
|
10
|
-
<div className="container header-inner">
|
|
11
|
-
<a href="/" className="logo">⚛️ react-html</a>
|
|
12
|
-
<nav>
|
|
13
|
-
<a href="/" className={currentPath === '/' ? 'active' : ''}>Home (Interactive)</a>
|
|
14
|
-
<a href="/pure.html" className={currentPath === '/pure' ? 'active' : ''}>Pure HTML (0 KB JS)</a>
|
|
15
|
-
</nav>
|
|
16
|
-
</div>
|
|
17
|
-
</header>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React, { ReactNode } from 'react';
|
|
2
|
-
import { Header } from './Header.js';
|
|
3
|
-
import { Footer } from './Footer.js';
|
|
4
|
-
|
|
5
|
-
export interface LayoutProps {
|
|
6
|
-
title?: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
currentPath?: string;
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function Layout({
|
|
13
|
-
title = 'react-html Demo',
|
|
14
|
-
description = 'Modular pure HTML generated with React',
|
|
15
|
-
currentPath = '/',
|
|
16
|
-
children
|
|
17
|
-
}: LayoutProps) {
|
|
18
|
-
return (
|
|
19
|
-
<html lang="en">
|
|
20
|
-
<head>
|
|
21
|
-
<meta charSet="UTF-8" />
|
|
22
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
23
|
-
<title>{title}</title>
|
|
24
|
-
<meta name="description" content={description} />
|
|
25
|
-
<link rel="stylesheet" href="/styles.css" />
|
|
26
|
-
</head>
|
|
27
|
-
<body>
|
|
28
|
-
<Header currentPath={currentPath} />
|
|
29
|
-
<main>
|
|
30
|
-
<div className="container">
|
|
31
|
-
{children}
|
|
32
|
-
</div>
|
|
33
|
-
</main>
|
|
34
|
-
<Footer />
|
|
35
|
-
</body>
|
|
36
|
-
</html>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|