Skip to content

Commit fd99028

Browse files
committed
SSG
1 parent 6728614 commit fd99028

File tree

13 files changed

+442
-199
lines changed

13 files changed

+442
-199
lines changed

.node-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v22.6.0
1+
v22

capra-fagradar/package.json

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "tsc -b && vite build",
8+
"build": "tsc -b && ./ssg.tsx",
99
"preview": "vite preview",
1010
"test": "echo 'no test'",
1111
"format": "biome format",
@@ -18,22 +18,24 @@
1818
"d3-scale": "^4.0.2",
1919
"react": "19.0.0",
2020
"react-dom": "19.0.0",
21-
"react-router-dom": "^6.26.0",
21+
"react-router": "0.0.0-nightly-d5ad0613b-20240620",
2222
"react-tooltip": "^5.28.0",
2323
"zustand": "^5.0.1"
2424
},
2525
"devDependencies": {
2626
"@biomejs/biome": "1.9.4",
27-
"@mdx-js/rollup": "^3.0.1",
28-
"@types/mdx": "^2.0.13",
29-
"@types/react": "^18.3.3",
30-
"@types/react-dom": "^18.3.0",
31-
"@vitejs/plugin-react-swc": "^3.5.0",
32-
"remark-frontmatter": "^5.0.0",
33-
"remark-gfm": "^4.0.0",
34-
"remark-mdx-frontmatter": "^5.0.0",
27+
"@mdx-js/rollup": "3.0.1",
28+
"@types/mdx": "2.0.13",
29+
"@types/node": "^22.10.2",
30+
"@types/react": "^19.0.0",
31+
"@types/react-dom": "^19.0.0",
32+
"@vitejs/plugin-react-swc": "3.5.0",
33+
"remark-frontmatter": "5.0.0",
34+
"remark-gfm": "4.0.0",
35+
"remark-mdx-frontmatter": "4.0.0",
36+
"stream-buffers": "3.0.3",
3537
"typescript": "^5.2.2",
36-
"vite": "^5.3.4"
37-
},
38-
"packageManager": "[email protected]+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
38+
"vite": "5.2.0",
39+
"vite-node": "1.6.0"
40+
}
3941
}

capra-fagradar/src/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Outlet, Link } from 'react-router-dom';
1+
import { Outlet, Link } from 'react-router';
22
import style from './app.module.css';
33
import { Breadcrumb } from './breadcrumb';
44
import { Header } from './header';

capra-fagradar/src/breadcrumb/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { FunctionComponent } from 'react';
22
import style from './breadcrumb.module.css';
3-
import { useMatches } from 'react-router-dom';
3+
import { useMatches } from 'react-router';
44

55
export const Breadcrumb = () => {
66
const matches = useMatches();

capra-fagradar/src/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Link } from 'react-router-dom';
1+
import { Link } from 'react-router';
22
import style from './app.module.css';
33
import Logo from './logo';
44

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
</head>
99
<body>
1010
<div id="root"></div>
11-
<script type="module" src="/src/main.tsx"></script>
11+
<script type="module" src="/main.tsx"></script>
1212
</body>
1313
</html>

capra-fagradar/src/main.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import React from 'react';
2-
import ReactDOM from 'react-dom/client';
3-
import { RouterProvider } from 'react-router-dom';
4-
import { router } from './routes.tsx';
2+
import { createRoot } from 'react-dom/client';
3+
import { createBrowserRouter, RouterProvider } from 'react-router';
4+
import routes from './routes';
55
import '@fontsource-variable/inter';
66

7-
ReactDOM.createRoot(document.getElementById('root')!).render(
7+
const router = createBrowserRouter(routes, { basename: import.meta.env.BASE_URL });
8+
const domNode = document.getElementById('root')!;
9+
10+
const root = createRoot(domNode);
11+
12+
root.render(
813
<React.StrictMode>
9-
<RouterProvider router={router} />
14+
<RouterProvider router={router} />
1015
</React.StrictMode>,
1116
);

capra-fagradar/src/routes.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createBrowserRouter, useRouteError, Link } from 'react-router-dom';
1+
import { useRouteError, Link } from 'react-router';
22
import { App, MainPage } from './app.tsx';
33
import { TechnicalRadar } from './technical-radar';
44
import { TechLeaderRadar } from './tech-leader-radar';
@@ -18,8 +18,7 @@ const ErrorPage = () => {
1818
);
1919
};
2020

21-
export const router = createBrowserRouter(
22-
[
21+
const routes = [
2322
{
2423
path: '/',
2524
element: <App />,
@@ -58,8 +57,6 @@ export const router = createBrowserRouter(
5857
},
5958
],
6059
},
61-
],
62-
{
63-
basename: import.meta.env.BASE_URL,
64-
},
65-
) as any;
60+
];
61+
62+
export default routes;

capra-fagradar/src/ssg-main.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import { hydrateRoot } from 'react-dom/client';
3+
import { createBrowserRouter, RouterProvider } from 'react-router';
4+
import routes from './routes';
5+
import '@fontsource-variable/inter';
6+
7+
const router = createBrowserRouter(routes, { basename: import.meta.env.BASE_URL });
8+
const domNode = document.getElementById('root')!;
9+
10+
hydrateRoot(
11+
domNode,
12+
<React.StrictMode>
13+
<RouterProvider router={router} />
14+
</React.StrictMode>,
15+
);

capra-fagradar/ssg-for-vite.tsx

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//
2+
// This file is distributed under the MIT License
3+
//
4+
import {
5+
createStaticHandler,
6+
createStaticRouter,
7+
StaticRouterProvider,
8+
} from 'react-router';
9+
import { renderToPipeableStream } from 'react-dom/server';
10+
import { finished } from 'node:stream/promises';
11+
import React from 'react';
12+
import { WritableStreamBuffer } from 'stream-buffers';
13+
import { Plugin } from 'vite';
14+
import { resolve } from 'path';
15+
16+
const routesToPaths = (routes, parentRoute = '') => {
17+
return routes.flatMap(({ path, children }) => {
18+
let currentPath = `${parentRoute}${path}`;
19+
20+
// Remove special case where the path is two repeating slashes
21+
currentPath = currentPath.replace(/(\/)+/g, '/');
22+
// Remove trailing '*'
23+
currentPath = currentPath.replace(/\*$/, '');
24+
25+
if(typeof children !== 'undefined') {
26+
return routesToPaths(children, currentPath);
27+
}
28+
29+
return currentPath;
30+
});
31+
};
32+
33+
const renderHtml = async (path, routes, mainScript) => {
34+
const { query, dataRoutes } = createStaticHandler(routes);
35+
36+
const url = new URL(path, 'http://localhost/')
37+
url.search = '';
38+
url.hash = '';
39+
url.pathname = path;
40+
41+
const context = await query(new Request(url.href, {
42+
signal: new AbortController().signal,
43+
}));
44+
45+
// If we got a redirect response, short circuit
46+
if (context instanceof Response) {
47+
throw context;
48+
}
49+
50+
const router = createStaticRouter(dataRoutes, context);
51+
52+
const app = <html lang="en">
53+
<head>
54+
<meta charSet="UTF-8" />
55+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
56+
</head>
57+
<body>
58+
<div id="root">
59+
<React.StrictMode>
60+
<StaticRouterProvider router={router} context={context} />
61+
</React.StrictMode>
62+
</div>
63+
<script type="module" src={mainScript}></script>
64+
</body>
65+
</html>;
66+
67+
const writableStream = new WritableStreamBuffer();
68+
const { pipe } = renderToPipeableStream(app, {
69+
onError(e) {
70+
throw e;
71+
},
72+
onAllReady() {
73+
pipe(writableStream);
74+
}
75+
});
76+
77+
await finished(writableStream);
78+
return writableStream.getContentsAsString('utf8');
79+
}
80+
81+
const fileNameFromPath = (path) => {
82+
let fileName = `${path}/index.html`;
83+
84+
fileName = fileName.replace(/(\/)+/g, '/');
85+
fileName = fileName.replace(/^\//, '');
86+
87+
fileName = resolve(__dirname, 'src', fileName);
88+
89+
return fileName;
90+
};
91+
92+
function ssgPlugin({ routes, mainScript }) : Plugin {
93+
const paths = routesToPaths(routes);
94+
95+
return {
96+
name: 'ssg-plugin',
97+
98+
// Add all routes in the `paths` array as chunks
99+
buildStart() {
100+
paths.forEach(path => {
101+
const id = fileNameFromPath(path);
102+
103+
this.emitFile({
104+
type: 'chunk',
105+
id,
106+
});
107+
});
108+
109+
},
110+
111+
// resolveId just matches up files that we later want to handle in load
112+
// without it we don't get to load the files in this plugin
113+
resolveId(id) {
114+
const file = paths.find(path => {
115+
const idFromPath = fileNameFromPath(path);
116+
return idFromPath === id;
117+
});
118+
119+
if(file) {
120+
return id;
121+
}
122+
},
123+
124+
// build page
125+
async load( id ) {
126+
const path = paths.find(path => {
127+
const idFromPath = fileNameFromPath(path);
128+
return idFromPath === id;
129+
});
130+
131+
if(path) {
132+
return await renderHtml(path, routes, mainScript);
133+
}
134+
135+
return null;
136+
},
137+
};
138+
}
139+
140+
export default ssgPlugin;

0 commit comments

Comments
 (0)