Skip to content

Commit 25ba052

Browse files
authored
fix react version detection and cloudflare build (#78)
* fix react version detection and cloudflare build
1 parent dc5deb6 commit 25ba052

File tree

27 files changed

+4669
-2991
lines changed

27 files changed

+4669
-2991
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* This is intended to be a basic starting point for linting in your app.
3+
* It relies on recommended configs out of the box for simplicity, but you can
4+
* and should modify this configuration to best suit your team's needs.
5+
*/
6+
7+
/** @type {import('eslint').Linter.Config} */
8+
module.exports = {
9+
root: true,
10+
parserOptions: {
11+
ecmaVersion: "latest",
12+
sourceType: "module",
13+
ecmaFeatures: {
14+
jsx: true,
15+
},
16+
},
17+
env: {
18+
browser: true,
19+
commonjs: true,
20+
es6: true,
21+
},
22+
ignorePatterns: ["!**/.server", "!**/.client"],
23+
24+
// Base config
25+
extends: ["eslint:recommended"],
26+
27+
overrides: [
28+
// React
29+
{
30+
files: ["**/*.{js,jsx,ts,tsx}"],
31+
plugins: ["react", "jsx-a11y"],
32+
extends: [
33+
"plugin:react/recommended",
34+
"plugin:react/jsx-runtime",
35+
"plugin:react-hooks/recommended",
36+
"plugin:jsx-a11y/recommended",
37+
],
38+
settings: {
39+
react: {
40+
version: "detect",
41+
},
42+
formComponents: ["Form"],
43+
linkComponents: [
44+
{ name: "Link", linkAttribute: "to" },
45+
{ name: "NavLink", linkAttribute: "to" },
46+
],
47+
"import/resolver": {
48+
typescript: {},
49+
},
50+
},
51+
},
52+
53+
// Typescript
54+
{
55+
files: ["**/*.{ts,tsx}"],
56+
plugins: ["@typescript-eslint", "import"],
57+
parser: "@typescript-eslint/parser",
58+
settings: {
59+
"import/internal-regex": "^~/",
60+
"import/resolver": {
61+
node: {
62+
extensions: [".ts", ".tsx"],
63+
},
64+
typescript: {
65+
alwaysTryTypes: true,
66+
},
67+
},
68+
},
69+
extends: ["plugin:@typescript-eslint/recommended", "plugin:import/recommended", "plugin:import/typescript"],
70+
},
71+
72+
// Node
73+
{
74+
files: [".eslintrc.cjs"],
75+
env: {
76+
node: true,
77+
},
78+
},
79+
],
80+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
.env
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Welcome to Remix + Vite!
2+
3+
📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/guides/vite) for details on supported features.
4+
5+
## Development
6+
7+
Run the Vite dev server:
8+
9+
```shellscript
10+
npm run dev
11+
```
12+
13+
## Deployment
14+
15+
First, build your app for production:
16+
17+
```sh
18+
npm run build
19+
```
20+
21+
Then run the app in production mode:
22+
23+
```sh
24+
npm start
25+
```
26+
27+
Now you'll need to pick a host to deploy it to.
28+
29+
### DIY
30+
31+
If you're familiar with deploying Node applications, the built-in Remix app server is production-ready.
32+
33+
Make sure to deploy the output of `npm run build`
34+
35+
- `build/server`
36+
- `build/client`
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function Input(props: React.ComponentPropsWithoutRef<"input">) {
2+
return <input type="text" placeholder="Input" {...props} />;
3+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { isbot } from "isbot";
2+
import { renderToReadableStream } from "react-dom/server";
3+
import type { AppLoadContext, EntryContext } from "react-router";
4+
import { ServerRouter } from "react-router";
5+
6+
export default async function handleRequest(
7+
request: Request,
8+
responseStatusCode: number,
9+
responseHeaders: Headers,
10+
routerContext: EntryContext,
11+
_loadContext: AppLoadContext
12+
) {
13+
let shellRendered = false;
14+
const userAgent = request.headers.get("user-agent");
15+
16+
const body = await renderToReadableStream(<ServerRouter context={routerContext} url={request.url} />, {
17+
onError(error: unknown) {
18+
responseStatusCode = 500;
19+
// Log streaming rendering errors from inside the shell. Don't log
20+
// errors encountered during initial shell rendering since they'll
21+
// reject and get logged in handleDocumentRequest.
22+
if (shellRendered) {
23+
console.error(error);
24+
}
25+
},
26+
});
27+
shellRendered = true;
28+
29+
// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
30+
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
31+
if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
32+
await body.allReady;
33+
}
34+
35+
responseHeaders.set("Content-Type", "text/html");
36+
return new Response(body, {
37+
headers: responseHeaders,
38+
status: responseStatusCode,
39+
});
40+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { LinksFunction } from "react-router";
2+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
3+
import styles from "~/styles/tailwind.css?url";
4+
5+
export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }];
6+
7+
export function Layout({ children }: { children: React.ReactNode }) {
8+
return (
9+
<html lang="en">
10+
<head>
11+
<meta charSet="utf-8" />
12+
<meta name="viewport" content="width=device-width, initial-scale=1" />
13+
<Meta />
14+
<Links />
15+
<link rel="stylesheet" href={styles} />
16+
</head>
17+
<body>
18+
{children}
19+
<ScrollRestoration />
20+
<Scripts />
21+
</body>
22+
</html>
23+
);
24+
}
25+
26+
export default function App() {
27+
return <Outlet />;
28+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { RouteConfig } from "@react-router/dev/routes";
2+
import { flatRoutes } from "@react-router/fs-routes";
3+
4+
export default flatRoutes() satisfies RouteConfig;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useState } from "react";
2+
import { useRevalidator } from "react-router";
3+
import { Input } from "~/components/input";
4+
import { getPublic } from "~/utils/.client/public";
5+
import { getCommon } from "~/utils/.common/common";
6+
import { getSecret } from "~/utils/.server/secret";
7+
import { getEnv } from "~/utils/env.server";
8+
import dbLogo from "/images/database.svg";
9+
import type { Route } from "./+types/_index";
10+
11+
export function loader() {
12+
console.log(getSecret(), getCommon());
13+
return {
14+
env: getEnv(),
15+
};
16+
}
17+
18+
export async function clientLoader({ serverLoader }: Route.ClientLoaderArgs) {
19+
console.log(getPublic(), getCommon());
20+
return {
21+
...(await serverLoader()),
22+
};
23+
}
24+
25+
clientLoader.hydrate = true;
26+
27+
export default function Index({ loaderData: data }: Route.ComponentProps) {
28+
const [value, setValue] = useState("");
29+
console.log("dbLogo", dbLogo);
30+
console.log("value", value);
31+
const { revalidate } = useRevalidator();
32+
return (
33+
<div className="min-h-screen bg-gray-100 flex flex-col items-center justify-center">
34+
<button type="button" onClick={revalidate} className="flex items-center gap-2">
35+
<img src={dbLogo} alt="Database" />
36+
Revalidate
37+
</button>
38+
<input />
39+
<Input value={value} onChange={(e) => setValue(e.target.value)} />
40+
<div className="mt-8 w-full max-w-4xl overflow-x-auto">
41+
<table className="w-full border-collapse bg-gray-100 shadow-md rounded-lg">
42+
<thead>
43+
<tr className="bg-gray-200">
44+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Key</th>
45+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-600 uppercase tracking-wider">Value</th>
46+
</tr>
47+
</thead>
48+
<tbody className="bg-white divide-y divide-gray-200">
49+
{Object.entries(data.env).map(([key, value]) => (
50+
<tr key={key} className="hover:bg-gray-50">
51+
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{key}</td>
52+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{value ?? "-"}</td>
53+
</tr>
54+
))}
55+
</tbody>
56+
</table>
57+
</div>
58+
</div>
59+
);
60+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from "react";
2+
import { Await, useLoaderData } from "react-router";
3+
import { Route } from "./+types/defer";
4+
5+
async function getProjectLocation() {
6+
return new Promise((resolve) => setTimeout(() => resolve("user/project"), 2000)) as Promise<string>;
7+
}
8+
9+
export async function loader() {
10+
return {
11+
project: getProjectLocation(),
12+
};
13+
}
14+
15+
export default function ProjectRoute({ loaderData }: Route.ComponentProps) {
16+
return (
17+
<main>
18+
<h1>Let's locate your project</h1>
19+
<React.Suspense fallback={<p>Loading project location...</p>}>
20+
<Await resolve={loaderData.project} errorElement={<p>Error loading project location!</p>}>
21+
{(location) => <p>Your project is at {location}.</p>}
22+
</Await>
23+
</React.Suspense>
24+
</main>
25+
);
26+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// generated by react-router-hono-server/dev
2+
import { createHonoServer } from "react-router-hono-server/cloudflare";
3+
4+
export default await createHonoServer();

0 commit comments

Comments
 (0)