From 908fb17af527925cd9058c69ec90fc285d636690 Mon Sep 17 00:00:00 2001 From: Carter Himmel Date: Fri, 27 Oct 2023 01:07:50 -0600 Subject: [PATCH] feat: ssg --- web/prerender.js | 34 ++++++++++++++++++++++++++++++++++ web/src/entry-client.tsx | 18 ++++++++++++++++++ web/src/entry-server.tsx | 11 +++++++++++ web/src/pages/Home.tsx | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 web/prerender.js create mode 100644 web/src/entry-client.tsx create mode 100644 web/src/entry-server.tsx create mode 100644 web/src/pages/Home.tsx diff --git a/web/prerender.js b/web/prerender.js new file mode 100644 index 0000000..147a4ef --- /dev/null +++ b/web/prerender.js @@ -0,0 +1,34 @@ +// Pre-render the app into static HTML. +// run `yarn generate` and then `dist/static` can be served as a static site. + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const toAbsolute = (p) => path.resolve(__dirname, p); + +const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8'); +const { render } = await import('./dist/server/entry-server.js'); + +// determine routes to pre-render from src/pages +const routesToPrerender = fs + .readdirSync(toAbsolute('src/pages')) + .map((file) => { + const name = file.replace(/\.tsx$/, '').toLowerCase() + return name === 'home' ? `/` : `/${name}` + }); + +(async () => { + // pre-render each route... + for (const url of routesToPrerender) { + const context = {} + const appHtml = await render(url, context) + + const html = template.replace(``, appHtml) + + const filePath = `dist/static${url === '/' ? '/index' : url}.html` + fs.writeFileSync(toAbsolute(filePath), html) + console.log('pre-rendered:', filePath) + } +})(); diff --git a/web/src/entry-client.tsx b/web/src/entry-client.tsx new file mode 100644 index 0000000..9531f98 --- /dev/null +++ b/web/src/entry-client.tsx @@ -0,0 +1,18 @@ +import {enableReactTracking} from '@legendapp/state/config/enableReactTracking'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; +import { BrowserRouter } from 'react-router-dom'; + +enableReactTracking({ + auto: true, +}); + +ReactDOM.hydrateRoot( + document.getElementById('app')!, + + + , +) +console.log('hydrated') diff --git a/web/src/entry-server.tsx b/web/src/entry-server.tsx new file mode 100644 index 0000000..203b456 --- /dev/null +++ b/web/src/entry-server.tsx @@ -0,0 +1,11 @@ +import ReactDOMServer from 'react-dom/server' +import { StaticRouter } from 'react-router-dom/server' +import App from './App.tsx'; + +export function render(url: string) { + return ReactDOMServer.renderToString( + + + , + ) +} diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx new file mode 100644 index 0000000..7022380 --- /dev/null +++ b/web/src/pages/Home.tsx @@ -0,0 +1,38 @@ +import {CrateHit, fetchCrateCount} from '../util/meili'; +import {ObservableObject} from '@legendapp/state'; +import {useObservable} from '@legendapp/state/react'; +import {performSearch} from '../util/search'; +import {Header} from '../components/Header'; +import {Main} from '../components/Main'; + +export type InnerAppState = { + hits: CrateHit[]; + count: number; + requestTime: string; +}; + +export type AppState = ObservableObject; + +export function Home() { + const state$ = useObservable({ + hits: [], + requestTime: '', + count: 0, + }); + performSearch(state$, ''); + fetchCrateCount(state$); + + return ( + <> +
+
+ + + ); +}