Skip to content

Commit

Permalink
Add playgrounds (#11464)
Browse files Browse the repository at this point in the history
  • Loading branch information
markdalgleish authored Apr 17, 2024
1 parent 0af95d4 commit f286ad7
Show file tree
Hide file tree
Showing 34 changed files with 951 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ node_modules/
pnpm-lock.yaml
/docs/api
examples/**/dist/
/playground/
/playground-local/
packages/**/dist/
packages/react-router-dom/server.d.ts
packages/react-router-dom/server.js
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ node_modules/
/examples/*/pnpm-lock.yaml
/examples/*/dist
/tutorial/dist
/playground-local/
/integration/playwright-report

# v5 build files
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"format": "prettier --ignore-path .eslintignore --write .",
"format:check": "prettier --ignore-path .eslintignore --check .",
"lint": "eslint --cache .",
"playground": "node ./scripts/playground.js",
"prerelease": "pnpm build",
"release": "changeset publish",
"size": "filesize",
Expand Down Expand Up @@ -99,6 +100,7 @@
"jest-environment-jsdom": "^29.6.2",
"jsonfile": "^6.1.0",
"prettier": "^2.8.8",
"prompts": "^2.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0",
Expand Down
4 changes: 4 additions & 0 deletions playground/compiler-express/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules

/build
.env
12 changes: 12 additions & 0 deletions playground/compiler-express/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RouterProvider } from "react-router-dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RouterProvider />
</StrictMode>
);
});
131 changes: 131 additions & 0 deletions playground/compiler-express/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { PassThrough } from "node:stream";

import type { AppLoadContext, EntryContext } from "@react-router/node";
import { createReadableStreamFromReadable } from "@react-router/node";
import { RemixServer } from "react-router-dom";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";

const ABORT_DELAY = 5_000;

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
reactRouterContext: EntryContext,
loadContext: AppLoadContext
) {
return isbot(request.headers.get("user-agent") || "")
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
reactRouterContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
reactRouterContext
);
}

function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
reactRouterContext: EntryContext
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={reactRouterContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onAllReady() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}

function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
reactRouterContext: EntryContext
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={reactRouterContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onShellReady() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}
29 changes: 29 additions & 0 deletions playground/compiler-express/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router-dom";

export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

export default function App() {
return <Outlet />;
}
16 changes: 16 additions & 0 deletions playground/compiler-express/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { MetaFunction } from "@react-router/node";

export const meta: MetaFunction = () => {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
};

export default function Index() {
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<h1>Welcome to React Router</h1>
</div>
);
}
38 changes: 38 additions & 0 deletions playground/compiler-express/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "node ./server.js",
"start": "cross-env NODE_ENV=production node ./server.js",
"typecheck": "tsc"
},
"dependencies": {
"@react-router/express": "workspace:*",
"@react-router/node": "workspace:*",
"compression": "^1.7.4",
"express": "^4.18.2",
"isbot": "^4.1.0",
"morgan": "^1.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "workspace:*",
"react-router-dom": "workspace:*"
},
"devDependencies": {
"@react-router/dev": "workspace:*",
"@types/compression": "^1.7.5",
"@types/express": "^4.17.20",
"@types/morgan": "^1.9.9",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"cross-env": "^7.0.3",
"typescript": "^5.1.6",
"vite": "^5.1.0",
"vite-tsconfig-paths": "^4.2.1"
},
"engines": {
"node": ">=18.0.0"
}
}
Binary file added playground/compiler-express/public/favicon.ico
Binary file not shown.
46 changes: 46 additions & 0 deletions playground/compiler-express/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createRequestHandler } from "@react-router/express";
import { installGlobals } from "@react-router/node";
import compression from "compression";
import express from "express";
import morgan from "morgan";

installGlobals();

const viteDevServer =
process.env.NODE_ENV === "production"
? undefined
: await import("vite").then((vite) =>
vite.createServer({
server: { middlewareMode: true },
})
);

const reactRouterHandler = createRequestHandler({
build: viteDevServer
? () => viteDevServer.ssrLoadModule("virtual:react-router/server-build")
: await import("./build/server/index.js"),
});

const app = express();

app.use(compression());
app.disable("x-powered-by");

if (viteDevServer) {
app.use(viteDevServer.middlewares);
} else {
app.use(
"/assets",
express.static("build/client/assets", { immutable: true, maxAge: "1y" })
);
}

app.use(express.static("build/client", { maxAge: "1h" }));
app.use(morgan("tiny"));

app.all("*", reactRouterHandler);

const port = process.env.PORT || 3000;
app.listen(port, () =>
console.log(`Express server listening at http://localhost:${port}`)
);
30 changes: 30 additions & 0 deletions playground/compiler-express/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"include": [
"**/*.ts",
"**/*.tsx",
"**/.server/**/*.ts",
"**/.server/**/*.tsx",
"**/.client/**/*.ts",
"**/.client/**/*.tsx"
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["@react-router/node", "vite/client"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"target": "ES2022",
"strict": true,
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"noEmit": true
}
}
7 changes: 7 additions & 0 deletions playground/compiler-express/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { vitePlugin as reactRouter } from "@react-router/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [reactRouter(), tsconfigPaths()],
});
4 changes: 4 additions & 0 deletions playground/compiler-spa/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules

/build
.env
12 changes: 12 additions & 0 deletions playground/compiler-spa/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RouterProvider } from "react-router-dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RouterProvider />
</StrictMode>
);
});
19 changes: 19 additions & 0 deletions playground/compiler-spa/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { EntryContext } from "@react-router/node";
import { RemixServer } from "react-router-dom";
import { renderToString } from "react-dom/server";

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
reactRouterContext: EntryContext
) {
let html = renderToString(
<RemixServer context={reactRouterContext} url={request.url} />
);
html = "<!DOCTYPE html>\n" + html;
return new Response(html, {
headers: { "Content-Type": "text/html" },
status: responseStatusCode,
});
}
Loading

0 comments on commit f286ad7

Please # to comment.