Skip to content

Commit b6dc64a

Browse files
committed
wip
1 parent 63cdc12 commit b6dc64a

34 files changed

+1192
-40
lines changed

packages/react-rsc/CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Changelog
2+
3+
### Dependencies
4+
5+
* The following workspace dependencies were updated
6+
* dependencies
7+
* @impalajs/core bumped to 0.0.6
8+
9+
## 0.0.5 (2023-03-28)
10+
11+
12+
### Features
13+
14+
* add dev server ([#2](https://github.com/ascorbic/impala/issues/2)) ([bab917a](https://github.com/ascorbic/impala/commit/bab917a28df70d9df691f7d1db61bf6e140b7acb))
15+
* **create-impala:** add create-impala cli ([d40b61c](https://github.com/ascorbic/impala/commit/d40b61c469223bc88d62fce156790ecaf2090e49))
16+
17+
18+
### Bug Fixes
19+
20+
* pass props to pages properly ([6b18945](https://github.com/ascorbic/impala/commit/6b189453d821ad85fdf828f5d270c754fecb0b26))
21+
* pass props to pages properly ([#3](https://github.com/ascorbic/impala/issues/3)) ([ee3bb82](https://github.com/ascorbic/impala/commit/ee3bb8279987dcdd0655ef02a53bad883ee3413a))
22+
23+
24+
### Dependencies
25+
26+
* The following workspace dependencies were updated
27+
* dependencies
28+
* @impalajs/core bumped to 0.0.5

packages/react-rsc/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Impala
2+
3+
<p align="center">
4+
5+
![impala](https://user-images.githubusercontent.com/213306/227727009-a4dc391f-efb1-4489-ad73-c3d3a327704a.png)
6+
7+
</p>

packages/react-rsc/client.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./dist/client";

packages/react-rsc/head.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Head } from "./dist/head";

packages/react-rsc/package.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@impalajs/react-rsc",
3+
"version": "0.0.6",
4+
"description": "",
5+
"scripts": {
6+
"build": "tsup src/index.ts src/client.tsx src/head.tsx --external virtual:client-bundle-map --external react --external react-server-dom-webpack --format cjs,esm --dts --clean"
7+
},
8+
"main": "./dist/index.js",
9+
"module": "./dist/index.mjs",
10+
"types": "./dist/index.d.ts",
11+
"exports": {
12+
".": {
13+
"types": "./dist/index.d.ts",
14+
"require": "./dist/index.js",
15+
"import": "./dist/index.mjs"
16+
},
17+
"./client": {
18+
"types": "./dist/client.d.ts",
19+
"require": "./dist/client.js",
20+
"import": "./dist/client.mjs"
21+
},
22+
"./head": {
23+
"types": "./dist/head.d.ts",
24+
"require": "./dist/head.js",
25+
"import": "./dist/head.mjs"
26+
}
27+
},
28+
"keywords": [],
29+
"author": "",
30+
"license": "MIT",
31+
"dependencies": {
32+
"@impalajs/core": "workspace:*"
33+
},
34+
"devDependencies": {
35+
"@types/node": "^18.15.7",
36+
"@types/react": "^18.0.28",
37+
"@types/react-dom": "^18.0.11",
38+
"react": "0.0.0-experimental-8310854ce-20230331",
39+
"react-dom": "0.0.0-experimental-8310854ce-20230331",
40+
"react-server-dom-webpack": "0.0.0-experimental-8310854ce-20230331",
41+
"tsup": "^6.7.0"
42+
},
43+
"peerDependencies": {
44+
"react": ">=18",
45+
"react-dom": ">=18"
46+
},
47+
"publishConfig": {
48+
"access": "public"
49+
}
50+
}

packages/react-rsc/src/client.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type {
2+
ModuleImports,
3+
RouteModule as CoreRouteModule,
4+
} from "@impalajs/core";
5+
import type { ElementType } from "react";
6+
import ReactDOM from "react-dom/client";
7+
8+
export type RouteModule = CoreRouteModule<ElementType>;
9+
export function clientBootstrap(modules: ModuleImports<RouteModule>) {
10+
const context = (window as any).___CONTEXT;
11+
12+
if (context && "chunk" in context) {
13+
const mod = modules[context.chunk];
14+
if (mod) {
15+
mod().then(({ default: Page }) => {
16+
ReactDOM.hydrateRoot(
17+
document.getElementById("__impala")!,
18+
<Page {...context} />
19+
);
20+
});
21+
} else {
22+
console.error(
23+
`[Impala] Could not hydrate page. Module not found: ${context?.chunk}`
24+
);
25+
}
26+
} else {
27+
console.log("[Impala] No context found. Skipping hydration.");
28+
}
29+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { renderToStaticMarkup } from "react-dom/server";
2+
// @ts-ignore
3+
import * as ReactServerDom from "react-server-dom-webpack/server.node";
4+
// @ts-ignore
5+
import * as ReactClient from "react-server-dom-webpack/client.node";
6+
7+
import { ElementType, useContext } from "react";
8+
import type { Context, RouteModule } from "@impalajs/core";
9+
import { Writable, WritableOptions } from "node:stream";
10+
import { HeadContext } from "./head-context";
11+
import { promises as fs } from "node:fs";
12+
13+
class StringResponse extends Writable {
14+
private buffer: string;
15+
private responseData: Promise<string>;
16+
constructor(options?: WritableOptions) {
17+
super(options);
18+
this.buffer = "";
19+
this.responseData = new Promise((resolve, reject) => {
20+
this.on("finish", () => resolve(this.buffer));
21+
this.on("error", reject);
22+
});
23+
}
24+
25+
_write(
26+
chunk: any,
27+
encoding: BufferEncoding,
28+
callback: (error?: Error | null) => void
29+
): void {
30+
console.log("chunk", chunk);
31+
this.buffer += chunk;
32+
callback();
33+
}
34+
35+
getData(): Promise<string> {
36+
return this.responseData;
37+
}
38+
}
39+
40+
function HeadContent() {
41+
const headProvider = useContext(HeadContext);
42+
return <>{...headProvider.getHead()}</>;
43+
}
44+
45+
export async function render(
46+
context: Context,
47+
mod: () => Promise<RouteModule<ElementType>>,
48+
bootstrapModules?: Array<string>
49+
) {
50+
// @ts-ignore
51+
const { default: bundleMapPath } = await import("virtual:client-bundle-map");
52+
console.log(bundleMapPath);
53+
const bundleMap = JSON.parse(await fs.readFile(bundleMapPath, "utf-8"));
54+
console.log(bundleMap);
55+
const { default: Page } = await mod();
56+
57+
const response = new StringResponse();
58+
59+
const stream = ReactServerDom.renderToPipeableStream(
60+
<Page {...context} />,
61+
{
62+
bootstrapModules,
63+
bootstrapScriptContent: `window.___CONTEXT=${JSON.stringify(context)};`,
64+
},
65+
bundleMap
66+
);
67+
68+
console.log(stream);
69+
70+
const something = ReactClient.createFromNodeStream(stream, bundleMap);
71+
console.log(something);
72+
const body = await response.getData();
73+
74+
const head = renderToStaticMarkup(<HeadContent />);
75+
76+
return { body, head };
77+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { createContext, ReactElement } from "react";
2+
import { renderToStaticMarkup } from "react-dom/server";
3+
4+
class HeadProvider {
5+
private head: React.ReactElement[] = [];
6+
7+
private removeTag(tag: string) {
8+
this.head = this.head.filter((item) => item.type !== tag);
9+
}
10+
11+
public addHead(head?: ReactElement | ReactElement[]) {
12+
if (!head) {
13+
return;
14+
}
15+
if (Array.isArray(head)) {
16+
// Can't have more than one title tag
17+
if (head.some((item) => item.type === "title")) {
18+
this.removeTag("title");
19+
}
20+
this.head.push(...head);
21+
return;
22+
}
23+
24+
if (head.type === "title") {
25+
this.removeTag("title");
26+
}
27+
this.head.push(head);
28+
}
29+
30+
public getHead() {
31+
return this.head;
32+
}
33+
34+
public getAsString() {
35+
return renderToStaticMarkup(<>{...this.head}</>);
36+
}
37+
}
38+
39+
const headProvider = new HeadProvider();
40+
41+
export const HeadContext = createContext(headProvider);

packages/react-rsc/src/head.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useContext } from "react";
2+
import { HeadContext } from "./head-context";
3+
4+
interface HeadProps {
5+
title?: string;
6+
children?: React.ReactElement | React.ReactElement[];
7+
}
8+
9+
export const Head: React.FC<HeadProps> = ({ children, title }) => {
10+
const headProvider = useContext(HeadContext);
11+
headProvider.addHead(children);
12+
if (title) {
13+
headProvider.addHead(<title>{title}</title>);
14+
}
15+
return null;
16+
};

packages/react-rsc/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type {
2+
DataModule,
3+
ModuleImports,
4+
RouteModule as CoreRouteModule,
5+
} from "@impalajs/core";
6+
import type { ElementType } from "react";
7+
export type RouteModule = CoreRouteModule<ElementType>;
8+
export type { DataModule };
9+
10+
export * from "./entry-server";

0 commit comments

Comments
 (0)