Skip to content

Commit

Permalink
feat: add PPR demo (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Jun 13, 2024
1 parent e5dc4af commit d17dfd6
Show file tree
Hide file tree
Showing 27 changed files with 1,639 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,22 @@ jobs:
- run: pnpm test-e2e
- run: pnpm build
- run: pnpm test-e2e-preview

ppr:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./ppr
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: corepack enable
- run: pnpm i
- run: pnpm lint-check
- run: pnpm tsc
- run: pnpm exec playwright install chromium
- run: pnpm test-e2e
- run: pnpm build
- run: pnpm test-e2e-preview
4 changes: 4 additions & 0 deletions ppr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.vercel
.wrangler
29 changes: 29 additions & 0 deletions ppr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Minimal PPR (Partial prerendering) demo based on React experimental API

- `postpone` from `react`
- `prerender` from `react-dom/static.edge`
- `resume` from `react-dom/server.edge`

Deployment

- https://ppr-experiment.hiro18181.workers.dev/?ppr
- https://ppr-experiment-hiroshi.vercel.app/?ppr (not working)

```sh
pnpm dev

# local preview (working)
pnpm build
pnpm preview

# cloudflare (working)
pnpm cf-build
pnpm cf-release

# cloudflare local preview (not working)
pnpm cf-preview

# vercel edge (not working)
pnpm vc-build
pnpm vc-release
```
9 changes: 9 additions & 0 deletions ppr/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"linter": { "enabled": false }
}
28 changes: 28 additions & 0 deletions ppr/e2e/basic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test } from "@playwright/test";

test("ssr", async ({ page }) => {
await page.goto("/", { waitUntil: "commit" });
await page.getByRole("heading", { name: "Static" }).click();
await page.getByText("Sleeping 1 sec").click();
await page.getByRole("heading", { name: "Dynamic 1" }).click();
await page.getByText("Sleeping 2 sec").click();
await page.getByRole("heading", { name: "Dynamic 2" }).click();
});

test("prerender and resume", async ({ page }) => {
await page.goto("/?prerender&resume", { waitUntil: "commit" });
await page.getByRole("heading", { name: "Static" }).click();
await page.getByText("Sleeping 1 sec").click();
await page.getByRole("heading", { name: "Dynamic 1" }).click();
await page.getByText("Sleeping 2 sec").click();
await page.getByRole("heading", { name: "Dynamic 2" }).click();
});

test("ppr @build", async ({ page }) => {
await page.goto("/?ppr", { waitUntil: "commit" });
await page.getByRole("heading", { name: "Static" }).click();
await page.getByText("Sleeping 1 sec").click();
await page.getByRole("heading", { name: "Dynamic 1" }).click();
await page.getByText("Sleeping 2 sec").click();
await page.getByRole("heading", { name: "Dynamic 2" }).click();
});
3 changes: 3 additions & 0 deletions ppr/misc/cf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# cloudflare

sciprts for Cloudflare Workers deployment
22 changes: 22 additions & 0 deletions ppr/misc/cf/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
set -eu -o pipefail

cd "$(dirname "${BASH_SOURCE[0]}")"

# clean
rm -rf dist
mkdir -p dist/server dist/client

# static
cp -r ../../public/. dist/client

# server (bundle by ourselve instead of relying on wrangler)
npx esbuild ../../dist/server/index.js \
--outfile=dist/server/index.js \
--metafile=dist/esbuild-metafile.json \
--define:process.env.NODE_ENV='"production"' \
--log-override:ignored-bare-import=silent \
--external:node:async_hooks \
--bundle \
--format=esm \
--platform=browser
7 changes: 7 additions & 0 deletions ppr/misc/cf/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name = "ppr-experiment"

main = "dist/server/index.js"
assets = "dist/client"
workers_dev = true
compatibility_date = "2024-01-01"
compatibility_flags = [ "nodejs_compat" ]
4 changes: 4 additions & 0 deletions ppr/misc/vercel/.vc-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"runtime": "edge",
"entrypoint": "index.js"
}
13 changes: 13 additions & 0 deletions ppr/misc/vercel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# vercel

Script for Vercel Build Output API (v3) based on https://github.com/hi-ogawa/reproductions/tree/main/vite-ssr-vercel-deploy

```sh
# initial project setup
vercel projects add ppr-experiment-hiroshi
vercel link -p ppr-experiment-hiroshi

# deploy
vercel deploy
vercel --prod
```
41 changes: 41 additions & 0 deletions ppr/misc/vercel/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cp, mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import * as esbuild from "esbuild";

async function main() {
// clean
await rm(".vercel/output", { recursive: true, force: true });
await mkdir(".vercel/output", { recursive: true });

// config
await cp(
join(import.meta.dirname, "config.json"),
".vercel/output/config.json",
);

// static
await mkdir(".vercel/output/static", { recursive: true });
await cp("public", ".vercel/output/static", { recursive: true });

// function
await mkdir(".vercel/output/functions/index.func", { recursive: true });
await cp(
join(import.meta.dirname, ".vc-config.json"),
".vercel/output/functions/index.func/.vc-config.json",
);

// bundle server
await esbuild.build({
entryPoints: ["dist/server/index.js"],
outfile: ".vercel/output/functions/index.func/index.js",
bundle: true,
format: "esm",
platform: "browser",
external: ["node:async_hooks"],
define: {
"process.env.NODE_ENV": `"production"`,
},
});
}

main();
18 changes: 18 additions & 0 deletions ppr/misc/vercel/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": 3,
"routes": [
{
"src": "^/assets/(.*)$",
"headers": {
"cache-control": "public, immutable, max-age=31536000"
}
},
{
"handle": "filesystem"
},
{
"src": ".*",
"dest": "/"
}
]
}
38 changes: 38 additions & 0 deletions ppr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@hiogawa/ppr-experiment",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build --ssr",
"preview": "vite preview",
"tsc": "tsc -b",
"tsc-dev": "pnpm tsc --watch --preserveWatchOutput",
"lint": "biome check --write .",
"lint-check": "biome check .",
"test-e2e": "playwright test",
"test-e2e-preview": "E2E_PREVIEW=1 playwright test",
"cf-build": "SSR_ENTRY=/src/adapters/cf pnpm build && bash misc/cf/build.sh",
"cf-preview": "cd misc/cf && wrangler dev",
"cf-release": "cd misc/cf && wrangler deploy",
"vc-build": "SSR_ENTRY=/src/adapters/vercel-edge pnpm build && node misc/vercel/build.js",
"vc-release": "vercel deploy --prebuilt"
},
"dependencies": {
"react": "0.0.0-experimental-a26e3f403e-20240611",
"react-dom": "0.0.0-experimental-a26e3f403e-20240611"
},
"devDependencies": {
"@biomejs/biome": "^1.8.1",
"@hiogawa/utils-node": "^0.0.1",
"@hiogawa/vite-plugin-ssr-middleware": "^0.0.3",
"@playwright/test": "^1.44.1",
"@types/node": "^20.14.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"esbuild": "^0.21.5",
"typescript": "^5.4.5",
"vite": "^5.2.13"
},
"packageManager": "[email protected]+sha512.ee7b93e0c2bd11409c6424f92b866f31d3ea1bef5fbe47d3c7500cdc3c9668833d2e55681ad66df5b640c61fa9dc25d546efa54d76d7f8bf54b13614ac293631"
}
32 changes: 32 additions & 0 deletions ppr/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { defineConfig, devices } from "@playwright/test";

const port = Number(process.env.E2E_PORT || 6174);
const isPreview = Boolean(process.env.E2E_PREVIEW);
const command = isPreview
? `pnpm preview --port ${port} --strict-port`
: `pnpm dev --port ${port} --strict-port`;

export default defineConfig({
testDir: "e2e",
use: {
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
viewport: null,
deviceScaleFactor: undefined,
},
},
],
webServer: {
command,
port,
},
grepInvert: isPreview ? /@dev/ : /@build/,
forbidOnly: !!process.env["CI"],
retries: process.env["CI"] ? 2 : 0,
reporter: "list",
});
Loading

0 comments on commit d17dfd6

Please sign in to comment.