add routing of static files to server for deploy

This commit is contained in:
Jo Franchetti 2024-09-26 18:32:44 +01:00
parent 728506de49
commit c95e91fa15
14 changed files with 3148 additions and 3109 deletions

View File

@ -2,17 +2,17 @@ module.exports = {
root: true, root: true,
env: { browser: true, es2020: true }, env: { browser: true, es2020: true },
extends: [ extends: [
'eslint:recommended', "eslint:recommended",
'plugin:@typescript-eslint/recommended', "plugin:@typescript-eslint/recommended",
'plugin:react-hooks/recommended', "plugin:react-hooks/recommended",
], ],
ignorePatterns: ['dist', '.eslintrc.cjs'], ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: '@typescript-eslint/parser', parser: "@typescript-eslint/parser",
plugins: ['react-refresh'], plugins: ["react-refresh"],
rules: { rules: {
'react-refresh/only-export-components': [ "react-refresh/only-export-components": [
'warn', "warn",
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
}, },
} };

View File

@ -1,15 +1,19 @@
# React + TypeScript + Vite # React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. This template provides a minimal setup to get React working in Vite with HMR and
some ESLint rules.
Currently, two official plugins are available: Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc)
uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration ## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: If you are developing a production application, we recommend updating the
configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this: - Configure the top-level `parserOptions` property like this:
@ -17,14 +21,19 @@ If you are developing a production application, we recommend updating the config
export default { export default {
// other rules... // other rules...
parserOptions: { parserOptions: {
ecmaVersion: 'latest', ecmaVersion: "latest",
sourceType: 'module', sourceType: "module",
project: ['./tsconfig.json', './tsconfig.node.json', './tsconfig.app.json'], project: ["./tsconfig.json", "./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
}, },
} };
``` ```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` - Replace `plugin:@typescript-eslint/recommended` to
`plugin:@typescript-eslint/recommended-type-checked` or
`plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list - Install
[eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and
add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends`
list

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,33 @@
import { Application, Router } from "@oak/oak"; import { Application, Router } from "@oak/oak";
import { oakCors } from "@tajpouria/cors"; import { oakCors } from "@tajpouria/cors";
import data from "./data.json" with { type: "json" }; import data from "./data.json" with { type: "json" };
import routeStaticFilesFrom from "./util/routeStaticFilesFrom.ts";
const router = new Router(); const router = new Router();
router.get("/", (context) => { router.get("/api/dinosaurs", (context) => {
context.response.body = "Welcome to dinosaur API!"; context.response.body = data;
}) });
router.get("/dinosaurs", (context) => { router.get("/api/dinosaurs/:dinosaur", (context) => {
context.response.body = data; if (!context?.params?.dinosaur) {
}) context.response.body = "No dinosaur name provided.";
}
router.get("/dinosaurs/:dinosaur", (context) => { const dinosaur = data.find((item) =>
if (!context?.params?.dinosaur) { item.name.toLowerCase() === context.params.dinosaur.toLowerCase()
context.response.body = "No dinosaur name provided."; );
}
const dinosaur = data.find((item) => context.response.body = dinosaur ? dinosaur : "No dinosaur found.";
item.name.toLowerCase() === context.params.dinosaur.toLowerCase()
);
context.response.body = dinosaur ? dinosaur : "No dinosaur found.";
}); });
const app = new Application(); const app = new Application();
app.use(oakCors()); app.use(oakCors());
app.use(router.routes()); app.use(router.routes());
app.use(router.allowedMethods()); app.use(router.allowedMethods());
app.use(routeStaticFilesFrom([
`${Deno.cwd()}/dist`,
`${Deno.cwd()}/public`,
]));
await app.listen({ port: 8000 }); await app.listen({ port: 8000 });

View File

@ -0,0 +1,19 @@
import { Next } from "jsr:@oak/oak/middleware";
import { Context } from "jsr:@oak/oak/context";
// Configure static site routes so that we can serve
// the Vite build output and the public folder
export default function routeStaticFilesFrom(staticPaths: string[]) {
return async (context: Context<Record<string, object>>, next: Next) => {
for (const path of staticPaths) {
try {
await context.send({ root: path, index: "index.html" });
return;
} catch {
continue;
}
}
await next();
};
}

View File

@ -3,6 +3,7 @@
"packages": { "packages": {
"specifiers": { "specifiers": {
"jsr:@oak/commons@^1.0": "jsr:@oak/commons@1.0.0", "jsr:@oak/commons@^1.0": "jsr:@oak/commons@1.0.0",
"jsr:@oak/oak": "jsr:@oak/oak@17.0.0",
"jsr:@oak/oak@^17.0.0": "jsr:@oak/oak@17.0.0", "jsr:@oak/oak@^17.0.0": "jsr:@oak/oak@17.0.0",
"jsr:@std/assert@^1.0": "jsr:@std/assert@1.0.6", "jsr:@std/assert@^1.0": "jsr:@std/assert@1.0.6",
"jsr:@std/bytes@^1.0": "jsr:@std/bytes@1.0.2", "jsr:@std/bytes@^1.0": "jsr:@std/bytes@1.0.2",
@ -27,6 +28,7 @@
"npm:react-dom@^18.3.1": "npm:react-dom@18.3.1_react@18.3.1", "npm:react-dom@^18.3.1": "npm:react-dom@18.3.1_react@18.3.1",
"npm:react-router-dom@^6.26.2": "npm:react-router-dom@6.26.2_react@18.3.1_react-dom@18.3.1__react@18.3.1", "npm:react-router-dom@^6.26.2": "npm:react-router-dom@6.26.2_react@18.3.1_react-dom@18.3.1__react@18.3.1",
"npm:react@^18.3.1": "npm:react@18.3.1", "npm:react@^18.3.1": "npm:react@18.3.1",
"npm:typescript@5.6.2": "npm:typescript@5.6.2",
"npm:typescript@^5.2.2": "npm:typescript@5.6.2", "npm:typescript@^5.2.2": "npm:typescript@5.6.2",
"npm:vite": "npm:vite@5.4.8", "npm:vite": "npm:vite@5.4.8",
"npm:vite@5.4.8": "npm:vite@5.4.8", "npm:vite@5.4.8": "npm:vite@5.4.8",

View File

@ -5,9 +5,10 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "deno task dev:api & deno task dev:vite", "dev": "deno task dev:api & deno task dev:vite",
"dev:api": "deno run --allow-env --allow-net api/main.ts", "dev:api": "deno run --allow-env --allow-read --allow-net api/main.ts",
"dev:vite": "deno run -A npm:vite", "dev:vite": "deno run -A npm:vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"serve": "deno task build && deno task dev:api",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview" "preview": "vite preview"
}, },

View File

@ -1,16 +1,16 @@
import { Route, Routes } from "react-router-dom"; import { BrowserRouter, Route, Routes } from "react-router-dom";
import Index from "./pages/index"; import Index from "./pages/index";
import Dinosaur from "./pages/Dinosaur"; import Dinosaur from "./pages/Dinosaur";
import "./App.css"; import "./App.css";
function App() { function App() {
return ( return (
<div> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
<Route path="/:selectedDinosaur" element={<Dinosaur />} /> <Route path="/:selectedDinosaur" element={<Dinosaur />} />
</Routes> </Routes>
</div> </BrowserRouter>
); );
} }

View File

@ -1,10 +1,7 @@
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import App from "./App"; import App from "./App";
import { BrowserRouter } from "react-router-dom";
import "./index.css"; import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<BrowserRouter> <App />,
<App />
</BrowserRouter>,
); );

View File

@ -1,26 +1,24 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { Dino } from "../../types"; import { Dino } from "../types";
export default function Dinosaur() { export default function Dinosaur() {
const { selectedDinosaur } = useParams(); const { selectedDinosaur } = useParams();
const [dinosaur, setDino] = useState<Dino>({ name: "", description: "" }); const [dinosaur, setDino] = useState<Dino>({ name: "", description: "" });
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const resp = await fetch( const resp = await fetch(`/api/dinosaurs/${selectedDinosaur}`);
`http://localhost:8000/dinosaurs/${selectedDinosaur}`, const dino = await resp.json() as Dino;
); setDino(dino);
const dino = await resp.json() as Dino; })();
setDino(dino); }, [selectedDinosaur]);
})();
}, []);
return ( return (
<div> <div>
<h1>{dinosaur.name}</h1> <h1>{dinosaur.name}</h1>
<p>{dinosaur.description}</p> <p>{dinosaur.description}</p>
<Link to="/">🠠 Back to all dinosaurs</Link> <Link to="/">🠠 Back to all dinosaurs</Link>
</div> </div>
); );
} }

View File

@ -1,29 +1,33 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Dino } from "../../types.ts"; import { Dino } from "../types.ts";
export default function Index() { export default function Index() {
const [dinosaurs, setDinosaurs] = useState<Dino[]>([]); const [dinosaurs, setDinosaurs] = useState<Dino[]>([]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const response = await fetch(`http://localhost:8000/dinosaurs/`); const response = await fetch(`/api/dinosaurs/`);
const allDinosaurs = await response.json() as Dino[]; const allDinosaurs = await response.json() as Dino[];
setDinosaurs(allDinosaurs); setDinosaurs(allDinosaurs);
})(); })();
}, []); }, []);
return ( return (
<main> <main>
<h1>Welcome to the Dinosaur app</h1> <h1>Welcome to the Dinosaur app</h1>
<p>Click on a dinosaur below to learn more.</p> <p>Click on a dinosaur below to learn more.</p>
{dinosaurs.map((dinosaur: Dino) => { {dinosaurs.map((dinosaur: Dino) => {
return ( return (
<Link to={`/${dinosaur.name.toLowerCase()}`} key={dinosaur.name} className="dinosaur"> <Link
{dinosaur.name} to={`/${dinosaur.name.toLowerCase()}`}
</Link> key={dinosaur.name}
); className="dinosaur"
})} >
</main> {dinosaur.name}
); </Link>
);
})}
</main>
);
} }

View File

@ -23,5 +23,5 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": ["src"] "include": ["src", "tsconfig.json"]
} }

View File

@ -1,7 +1,15 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
}) server: {
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
},
},
});