Skip to content

Commit 4f71266

Browse files
committed
feat(funstack-static): initial implementation
1 parent f3b144b commit 4f71266

32 files changed

Lines changed: 2142 additions & 17 deletions

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## FUNSTACK Static
2+
3+
A maximally minimal React framework.
4+
5+
> ![WARNING]
6+
> This is work in progress.
7+
8+
**Features:**
9+
10+
- :x: **No server runs** - perfect for CSR (Client Side Rendering) app and static deployment.
11+
- :white_check_mark: **RSC support** - React Server Components are supported even without a server which helps reduce bundle size and improve performance.
12+
- :white_check_mark: **Vite-based** - so minimal that this framework is served as a Vite plugin (based on [@vitejs/plugin-rsc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc#readme))
13+
14+
### Usage
15+
16+
See [the example project](./packages/example/vite.config.ts) for complete usage.
17+
18+
```ts
19+
// vite.config.ts
20+
import funstackStatic from "@funstack/static";
21+
import react from "@vitejs/plugin-react";
22+
import { defineConfig } from "vite";
23+
24+
export default defineConfig({
25+
plugins: [
26+
funstackStatic({
27+
root: "./src/root.tsx",
28+
app: "./src/App.tsx",
29+
}),
30+
react(),
31+
],
32+
});
33+
```
34+
35+
## License
36+
37+
MIT

packages/example/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

packages/example/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Vite + RSC
2+
3+
This example shows how to set up a React application with [Server Component](https://react.dev/reference/rsc/server-components) features on Vite using [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc).
4+
5+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc/examples/starter)
6+
7+
```sh
8+
# run dev server
9+
npm run dev
10+
11+
# build for production and preview
12+
npm run build
13+
npm run preview
14+
```
15+
16+
## API usage
17+
18+
See [`@vitejs/plugin-rsc`](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc) for the documentation.
19+
20+
- [`vite.config.ts`](./vite.config.ts)
21+
- `@vitejs/plugin-rsc/plugin`
22+
- [`./src/framework/entry.rsc.tsx`](./src/framework/entry.rsc.tsx)
23+
- `@vitejs/plugin-rsc/rsc`
24+
- `import.meta.viteRsc.loadModule`
25+
- [`./src/framework/entry.ssr.tsx`](./src/framework/entry.ssr.tsx)
26+
- `@vitejs/plugin-rsc/ssr`
27+
- `import.meta.viteRsc.loadBootstrapScriptContent`
28+
- `rsc-html-stream/server`
29+
- [`./src/framework/entry.browser.tsx`](./src/framework/entry.browser.tsx)
30+
- `@vitejs/plugin-rsc/browser`
31+
- `rsc-html-stream/client`
32+
33+
## Notes
34+
35+
- [`./src/framework/entry.{browser,rsc,ssr}.tsx`](./src/framework) (with inline comments) provides an overview of how low level RSC (React flight) API can be used to build RSC framework.
36+
- You can use [`vite-plugin-inspect`](https://github.com/antfu-collective/vite-plugin-inspect) to understand how `"use client"` and `"use server"` directives are transformed internally.
37+
38+
## Deployment
39+
40+
See [vite-plugin-rsc-deploy-example](https://github.com/hi-ogawa/vite-plugin-rsc-deploy-example)

packages/example/package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "funstack-static-example",
3+
"version": "0.0.0",
4+
"private": true,
5+
"license": "MIT",
6+
"type": "module",
7+
"scripts": {
8+
"dev": "vite",
9+
"build": "vite build",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"@types/node": "catalog:",
14+
"react": "catalog:",
15+
"react-dom": "catalog:",
16+
"@funstack/static": "workspace:*"
17+
},
18+
"devDependencies": {
19+
"@types/react": "^19.2.7",
20+
"@types/react-dom": "^19.2.3",
21+
"@vitejs/plugin-react": "latest",
22+
"rsc-html-stream": "^0.0.7",
23+
"vite": "catalog:"
24+
}
25+
}

packages/example/public/vite.svg

Lines changed: 1 addition & 0 deletions
Loading

packages/example/src/App.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import viteLogo from "/vite.svg";
2+
import reactLogo from "./assets/react.svg";
3+
import { ClientCounter } from "./client.tsx";
4+
5+
export default function App() {
6+
return (
7+
<div id="root">
8+
<div>
9+
<a href="https://vite.dev" target="_blank">
10+
<img src={viteLogo} className="logo" alt="Vite logo" />
11+
</a>
12+
<a
13+
href="https://react.dev/reference/rsc/server-components"
14+
target="_blank"
15+
>
16+
<img src={reactLogo} className="logo react" alt="React logo" />
17+
</a>
18+
</div>
19+
<h1>Vite + RSC</h1>
20+
<div className="card">
21+
<ClientCounter />
22+
</div>
23+
<ul className="read-the-docs">
24+
<li>
25+
Edit <code>src/client.tsx</code> to test client HMR.
26+
</li>
27+
<li>
28+
Edit <code>src/root.tsx</code> to test server HMR.
29+
</li>
30+
</ul>
31+
</div>
32+
);
33+
}
Lines changed: 1 addition & 0 deletions
Loading

packages/example/src/client.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use client";
2+
3+
import React from "react";
4+
5+
export function ClientCounter() {
6+
const [count, setCount] = React.useState(0);
7+
8+
return (
9+
<button onClick={() => setCount((count) => count + 1)}>
10+
Client Counter: {count}
11+
</button>
12+
);
13+
}

packages/example/src/index.css

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
:root {
2+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3+
line-height: 1.5;
4+
font-weight: 400;
5+
6+
color-scheme: light dark;
7+
color: rgba(255, 255, 255, 0.87);
8+
background-color: #242424;
9+
10+
font-synthesis: none;
11+
text-rendering: optimizeLegibility;
12+
-webkit-font-smoothing: antialiased;
13+
-moz-osx-font-smoothing: grayscale;
14+
}
15+
16+
a {
17+
font-weight: 500;
18+
color: #646cff;
19+
text-decoration: inherit;
20+
}
21+
a:hover {
22+
color: #535bf2;
23+
}
24+
25+
body {
26+
margin: 0;
27+
display: flex;
28+
flex-flow: column;
29+
place-items: center;
30+
min-width: 320px;
31+
min-height: 100vh;
32+
}
33+
34+
h1 {
35+
font-size: 3.2em;
36+
line-height: 1.1;
37+
}
38+
39+
button {
40+
border-radius: 8px;
41+
border: 1px solid transparent;
42+
padding: 0.6em 1.2em;
43+
font-size: 1em;
44+
font-weight: 500;
45+
font-family: inherit;
46+
background-color: #1a1a1a;
47+
cursor: pointer;
48+
transition: border-color 0.25s;
49+
}
50+
button:hover {
51+
border-color: #646cff;
52+
}
53+
button:focus,
54+
button:focus-visible {
55+
outline: 4px auto -webkit-focus-ring-color;
56+
}
57+
58+
@media (prefers-color-scheme: light) {
59+
:root {
60+
color: #213547;
61+
background-color: #ffffff;
62+
}
63+
a:hover {
64+
color: #747bff;
65+
}
66+
button {
67+
background-color: #f9f9f9;
68+
}
69+
}
70+
71+
#root {
72+
max-width: 1280px;
73+
margin: 0 auto;
74+
padding: 2rem;
75+
text-align: center;
76+
}
77+
78+
.logo {
79+
height: 6em;
80+
padding: 1.5em;
81+
will-change: filter;
82+
transition: filter 300ms;
83+
}
84+
.logo:hover {
85+
filter: drop-shadow(0 0 2em #646cffaa);
86+
}
87+
.logo.react:hover {
88+
filter: drop-shadow(0 0 2em #61dafbaa);
89+
}
90+
91+
@keyframes logo-spin {
92+
from {
93+
transform: rotate(0deg);
94+
}
95+
to {
96+
transform: rotate(360deg);
97+
}
98+
}
99+
100+
@media (prefers-reduced-motion: no-preference) {
101+
a:nth-of-type(2) .logo {
102+
animation: logo-spin infinite 20s linear;
103+
}
104+
}
105+
106+
.card {
107+
padding: 1rem;
108+
}
109+
110+
.read-the-docs {
111+
color: #888;
112+
text-align: left;
113+
}
114+
115+
.app {
116+
border: 1px solid #ccc;
117+
}

packages/example/src/root.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type React from "react";
2+
import "./index.css";
3+
4+
export default function Root({ children }: { children: React.ReactNode }) {
5+
return (
6+
<html lang="en">
7+
<head>
8+
<meta charSet="UTF-8" />
9+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
10+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
11+
<title>Vite + RSC</title>
12+
</head>
13+
<body>
14+
<h1>My RSC Application</h1>
15+
<div className="app">{children}</div>
16+
</body>
17+
</html>
18+
);
19+
}

0 commit comments

Comments
 (0)