@punkestu/react-html 0.0.0-stage → 0.1.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.
@@ -0,0 +1,184 @@
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
+
@@ -0,0 +1,17 @@
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
+
@@ -0,0 +1,35 @@
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;
@@ -0,0 +1,13 @@
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>&copy; {currentYear} react-html. Modular pure HTML engine with selective reactive islands.</p>
9
+ </div>
10
+ </footer>
11
+ );
12
+ }
13
+
@@ -0,0 +1,20 @@
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
+
@@ -0,0 +1,39 @@
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
+
@@ -0,0 +1,59 @@
1
+ import React, { useState } from 'react';
2
+ import { island } from '../../../src/index.js';
3
+
4
+ export interface TabItem {
5
+ id: string;
6
+ label: string;
7
+ content: string;
8
+ }
9
+
10
+ export interface TabsProps {
11
+ tabs?: TabItem[];
12
+ defaultTab?: string;
13
+ }
14
+
15
+ const defaultTabs: TabItem[] = [
16
+ {
17
+ id: 'features',
18
+ label: '✨ Features',
19
+ content: 'Write modular React components with standard JSX. Reusable layouts, cards, buttons, with 0 repetitive boilerplate.'
20
+ },
21
+ {
22
+ id: 'islands',
23
+ label: '🏝️ Islands',
24
+ content: 'Only reactive components compile to client-side JavaScript. Everything else is rendered to pure, clean, zero-JS HTML.'
25
+ },
26
+ {
27
+ id: 'speed',
28
+ label: '⚡ Speed',
29
+ content: 'Ultra-fast builds powered by esbuild. Instant page loads for users because the browser only receives semantic HTML.'
30
+ }
31
+ ];
32
+
33
+ function TabsComponent({ tabs = defaultTabs, defaultTab = 'features' }: TabsProps) {
34
+ const [activeTab, setActiveTab] = useState(defaultTab);
35
+ const currentTab = tabs.find((t) => t.id === activeTab) || tabs[0];
36
+
37
+ return (
38
+ <div className="tabs-container">
39
+ <div className="tabs-header">
40
+ {tabs.map((tab) => (
41
+ <button
42
+ key={tab.id}
43
+ type="button"
44
+ className={`tab-btn ${activeTab === tab.id ? 'active' : ''}`}
45
+ onClick={() => setActiveTab(tab.id)}
46
+ >
47
+ {tab.label}
48
+ </button>
49
+ ))}
50
+ </div>
51
+ <div className="tab-content">
52
+ <p>{currentTab.content}</p>
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export const Tabs = island(TabsComponent, import.meta.url, 'Tabs');
59
+ export default Tabs;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Layout } from '../components/Layout.js';
3
+ import { Card } from '../components/Card.js';
4
+ import { Counter } from '../components/Counter.island.js';
5
+ import { Tabs } from '../components/Tabs.island.js';
6
+
7
+ export const title = 'react-html Demo - Pure HTML + Reactive Islands';
8
+ export const description = 'Modular HTML with React and selective reactive island compilation';
9
+
10
+ export default function HomePage() {
11
+ return (
12
+ <Layout title={title} description={description} currentPath="/">
13
+ <h1>React to Pure HTML Engine</h1>
14
+ <p className="subtitle">
15
+ Write modular React components for static HTML, and compile only reactive components to minimal JavaScript.
16
+ </p>
17
+
18
+ <div className="grid">
19
+ <Card title="Modular Components" icon="📦">
20
+ <p>
21
+ Build reusable UI parts like headers, navigation, cards, and footers with React. No repetitive HTML code.
22
+ </p>
23
+ </Card>
24
+ <Card title="Zero-JS by Default" icon="🚀">
25
+ <p>
26
+ Static components render to 100% pure HTML. No client bundle is generated unless reactive islands are used.
27
+ </p>
28
+ </Card>
29
+ <Card title="Selective Reactivity" icon="🏝️">
30
+ <p>
31
+ Interactive components are isolated as islands and bundled into tiny, dedicated client scripts using esbuild.
32
+ </p>
33
+ </Card>
34
+ </div>
35
+
36
+ <h2 style={{ marginBottom: '1rem' }}>Interactive Island Demos</h2>
37
+ <p style={{ color: '#94a3b8', marginBottom: '1.5rem' }}>
38
+ Below are two reactive islands embedded in pure static HTML:
39
+ </p>
40
+
41
+ {/* Reactive Counter Island */}
42
+ <Counter initialCount={10} step={2} />
43
+
44
+ {/* Reactive Tabs Island */}
45
+ <Tabs defaultTab="islands" />
46
+ </Layout>
47
+ );
48
+ }
49
+
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { Layout } from '../components/Layout.js';
3
+ import { Card } from '../components/Card.js';
4
+
5
+ export const title = '100% Pure HTML - react-html';
6
+ export const description = 'Demonstration of zero JavaScript pure HTML output';
7
+
8
+ export default function PurePage() {
9
+ const features = [
10
+ {
11
+ title: 'Zero Runtime JS',
12
+ icon: '🛡️',
13
+ desc: 'This entire page is compiled to semantic HTML. There are no <script> tags, no React runtime, and no client-side JavaScript sent over the wire.'
14
+ },
15
+ {
16
+ title: 'Modular & DRY',
17
+ icon: '🧩',
18
+ desc: 'Built using React components like <Layout />, <Header />, <Card />, and <Footer />. No copy-pasting HTML boilerplate!'
19
+ },
20
+ {
21
+ title: 'Fast TTFB & Paint',
22
+ icon: '⚡',
23
+ desc: 'Browsers parse pure HTML almost instantly. Perfect for documentation, blogs, landing pages, and marketing sites.'
24
+ }
25
+ ];
26
+
27
+ return (
28
+ <Layout title={title} description={description} currentPath="/pure">
29
+ <h1>Pure Static HTML</h1>
30
+ <p className="subtitle">
31
+ Compiled from React JSX with <strong>0 KB client JavaScript</strong>.
32
+ </p>
33
+
34
+ <div className="grid">
35
+ {features.map((item, idx) => (
36
+ <Card key={idx} title={item.title} icon={item.icon}>
37
+ <p>{item.desc}</p>
38
+ </Card>
39
+ ))}
40
+ </div>
41
+
42
+ <div className="card">
43
+ <h3>Inspect the Source Code</h3>
44
+ <p>
45
+ Right-click and select "View Page Source". You will notice:
46
+ </p>
47
+ <ul style={{ paddingLeft: '1.5rem', marginTop: '0.75rem', color: '#94a3b8' }}>
48
+ <li>Clean, indented, semantic HTML5 markup.</li>
49
+ <li>No <code>data-reactroot</code> or React internal comments.</li>
50
+ <li>Zero <code>&lt;script&gt;</code> tags.</li>
51
+ </ul>
52
+ </div>
53
+ </Layout>
54
+ );
55
+ }
56
+
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@punkestu/react-html",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Engine to convert React/JSX to pure HTML with selective reactive island compilation",
5
+ "type": "module",
6
+ "bin": {
7
+ "react-html": "./bin/react-html.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./server": {
17
+ "import": "./dist/server/index.js",
18
+ "types": "./dist/server/index.d.ts"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "prepublishOnly": "npm run build",
24
+ "test": "node --test tests/**/*.test.mjs"
25
+ },
26
+ "keywords": [
27
+ "react",
28
+ "html",
29
+ "ssg",
30
+ "static-site-generator",
31
+ "islands-architecture",
32
+ "pure-html"
33
+ ],
34
+ "author": "",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "esbuild": "^0.25.0",
38
+ "react": "^19.0.0",
39
+ "react-dom": "^19.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.13.0",
43
+ "@types/react": "^19.0.0",
44
+ "@types/react-dom": "^19.0.0",
45
+ "typescript": "^5.7.0"
46
+ }
6
47
  }