Skip to content

Commit 3371aa0

Browse files
committed
Add example
1 parent 6109204 commit 3371aa0

File tree

11 files changed

+171
-1
lines changed

11 files changed

+171
-1
lines changed
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@parcel/config-react-ssg"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
5+
export function Counter() {
6+
let [count, setCount] = useState(0);
7+
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { PageProps } from "../types";
2+
3+
export function Nav({pages, currentPage}: PageProps) {
4+
return (
5+
<nav>
6+
<ul>
7+
{pages.map(page => (
8+
<li key={page.url}>
9+
<a href={page.url} aria-current={page.url === currentPage.url ? 'page' : undefined}>
10+
{page.name.replace('.html', '')}
11+
</a>
12+
</li>
13+
))}
14+
</ul>
15+
</nav>
16+
);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use client-entry";
2+
3+
import {useState, use, startTransition, useInsertionEffect, ReactElement} from 'react';
4+
import ReactDOM from 'react-dom/client';
5+
import {createFromReadableStream, createFromFetch} from 'react-server-dom-parcel/client';
6+
import {rscStream} from 'rsc-html-stream/client';
7+
8+
// Stream in initial RSC payload embedded in the HTML.
9+
let initialRSCPayload = createFromReadableStream<ReactElement>(rscStream);
10+
let updateRoot: ((root: ReactElement, cb?: (() => void) | null) => void) | null = null;
11+
12+
function Content() {
13+
// Store the current root element in state, along with a callback
14+
// to call once rendering is complete.
15+
let [[root, cb], setRoot] = useState<[ReactElement, (() => void) | null]>([use(initialRSCPayload), null]);
16+
updateRoot = (root, cb) => setRoot([root, cb ?? null]);
17+
useInsertionEffect(() => cb?.());
18+
return root;
19+
}
20+
21+
// Hydrate initial page content.
22+
startTransition(() => {
23+
ReactDOM.hydrateRoot(document, <Content />);
24+
});
25+
26+
// A very simple router. When we navigate, we'll fetch a new RSC payload from the server,
27+
// and in a React transition, stream in the new page. Once complete, we'll pushState to
28+
// update the URL in the browser.
29+
async function navigate(pathname: string, push = false) {
30+
let res = fetch(pathname.replace(/\.html$/, '.rsc'));
31+
let root = await createFromFetch<ReactElement>(res);
32+
startTransition(() => {
33+
updateRoot!(root, () => {
34+
if (push) {
35+
history.pushState(null, '', pathname);
36+
push = false;
37+
}
38+
});
39+
});
40+
}
41+
42+
// Intercept link clicks to perform RSC navigation.
43+
document.addEventListener('click', e => {
44+
let link = (e.target as Element).closest('a');
45+
if (
46+
link &&
47+
link instanceof HTMLAnchorElement &&
48+
link.href &&
49+
(!link.target || link.target === '_self') &&
50+
link.origin === location.origin &&
51+
!link.hasAttribute('download') &&
52+
e.button === 0 && // left clicks only
53+
!e.metaKey && // open in new tab (mac)
54+
!e.ctrlKey && // open in new tab (windows)
55+
!e.altKey && // download
56+
!e.shiftKey &&
57+
!e.defaultPrevented
58+
) {
59+
e.preventDefault();
60+
navigate(link.pathname, true);
61+
}
62+
});
63+
64+
// When the user clicks the back button, navigate with RSC.
65+
window.addEventListener('popstate', e => {
66+
navigate(location.pathname);
67+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
h1 {
2+
font-family: system-ui;
3+
}
4+
5+
nav a[aria-current] {
6+
background: black;
7+
color: white
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "parcel-rsc-static-example",
3+
"version": "0.0.0",
4+
"private": true,
5+
"source": "pages/*.tsx",
6+
"targets": {
7+
"default": {
8+
"context": "react-server",
9+
"scopeHoist": false
10+
}
11+
},
12+
"scripts": {
13+
"start": "parcel --config .parcelrc",
14+
"build": "parcel build"
15+
},
16+
"dependencies": {
17+
"react": "^19",
18+
"react-dom": "^19",
19+
"react-server-dom-parcel": "^0.0.1",
20+
"rsc-html-stream": "^0.0.4"
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { PageProps } from "../types";
2+
import { Resources } from "@parcel/runtime-rsc";
3+
import { Counter } from "../components/Counter";
4+
import { Nav } from '../components/Nav';
5+
import '../components/style.css';
6+
import '../components/client';
7+
8+
export default function Index({pages, currentPage}: PageProps) {
9+
return (
10+
<html>
11+
<head>
12+
<title>Static RSC</title>
13+
<Resources />
14+
</head>
15+
<body>
16+
<h1>This is an RSC!</h1>
17+
<Nav pages={pages} currentPage={currentPage} />
18+
<Counter />
19+
</body>
20+
</html>
21+
);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { PageProps } from "../types";
2+
import { Resources } from "@parcel/runtime-rsc";
3+
import {Nav} from '../components/Nav';
4+
import '../components/style.css';
5+
import '../components/client';
6+
7+
export default function Index({pages, currentPage}: PageProps) {
8+
return (
9+
<html>
10+
<head>
11+
<title>Static RSC</title>
12+
<Resources />
13+
</head>
14+
<body>
15+
<h1>This is another RSC!</h1>
16+
<Nav pages={pages} currentPage={currentPage} />
17+
</body>
18+
</html>
19+
);
20+
}

packages/examples/react-static/yarn.lock

Whitespace-only changes.

packages/packagers/react-ssg/src/ReactSSGPackager.js

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export default (new Packager({
125125

126126
return [
127127
{
128+
type: 'html',
128129
contents: Readable.from(response),
129130
},
130131
{

packages/reporters/cli/src/CLIReporter.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ export async function _report(
111111

112112
if (
113113
options.serveOptions &&
114-
event.bundleGraph.getEntryBundles().some(b => b.env.isBrowser())
114+
event.bundleGraph
115+
.getEntryBundles()
116+
.some(b => b.env.isBrowser() || b.type === 'html')
115117
) {
116118
persistMessage(
117119
chalk.blue.bold(

0 commit comments

Comments
 (0)