Skip to content

Commit b6fa187

Browse files
Copilotballoob
andcommitted
Configure GitHub Pages deployment with Vite and SPA routing
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
1 parent 13b7876 commit b6fa187

5 files changed

Lines changed: 141 additions & 2 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
10+
permissions:
11+
contents: read
12+
pages: write
13+
id-token: write
14+
15+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
16+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
17+
concurrency:
18+
group: "pages"
19+
cancel-in-progress: false
20+
21+
jobs:
22+
# Build job
23+
build:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Setup Node.js
30+
uses: actions/setup-node@v4
31+
with:
32+
node-version: '20'
33+
cache: 'npm'
34+
35+
- name: Install dependencies
36+
run: npm ci
37+
38+
- name: Lint
39+
run: npm run lint
40+
41+
- name: Build
42+
run: npm run build
43+
env:
44+
NODE_ENV: production
45+
46+
- name: Setup Pages
47+
uses: actions/configure-pages@v4
48+
49+
- name: Upload artifact
50+
uses: actions/upload-pages-artifact@v3
51+
with:
52+
path: ./dist
53+
54+
# Deployment job
55+
deploy:
56+
environment:
57+
name: github-pages
58+
url: ${{ steps.deployment.outputs.page_url }}
59+
runs-on: ubuntu-latest
60+
needs: build
61+
steps:
62+
- name: Deploy to GitHub Pages
63+
id: deployment
64+
uses: actions/deploy-pages@v4

index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@
1717
margin: 0;
1818
}
1919
</style>
20+
<script type="text/javascript">
21+
// Single Page Apps for GitHub Pages
22+
// MIT License
23+
// https://github.com/rafgraph/spa-github-pages
24+
// This script checks to see if a redirect is present in the query string,
25+
// converts it back into the correct url and adds it to the
26+
// browser's history using window.history.replaceState(...),
27+
// which won't cause the browser to attempt to load the new url.
28+
// When the single page app is loaded further down in this file,
29+
// the correct url will be waiting in the browser's history for
30+
// the single page app to route accordingly.
31+
(function(l) {
32+
if (l.search[1] === '/' ) {
33+
var decoded = l.search.slice(1).split('&').map(function(s) {
34+
return s.replace(/~and~/g, '&')
35+
}).join('?');
36+
window.history.replaceState(null, null,
37+
l.pathname.slice(0, -1) + decoded + l.hash
38+
);
39+
}
40+
}(window.location))
41+
</script>
2042
<script type="module" src="/src/main-app.ts"></script>
2143
</head>
2244
<body class="wa-palette-shoelace wa-theme-shoelace">

public/404.html

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Device Toolbox</title>
6+
<script type="text/javascript">
7+
// Single Page Apps for GitHub Pages
8+
// MIT License
9+
// https://github.com/rafgraph/spa-github-pages
10+
// This script takes the current url and converts the path and query
11+
// string into just a query string, and then redirects the browser
12+
// to the new url with only a query string and hash fragment,
13+
// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
14+
// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
15+
// Note: this 404.html file must be at least 512 bytes for it to work
16+
// with Internet Explorer (it is currently > 512 bytes)
17+
18+
// If you're creating a Project Pages site and NOT using a custom domain,
19+
// then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).
20+
// This way the code will only replace the route part and not the real directory in which the app resides,
21+
// for example: https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
22+
// https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe
23+
// Otherwise, leave pathSegmentsToKeep as 0.
24+
var pathSegmentsToKeep = 1;
25+
26+
var l = window.location;
27+
l.replace(
28+
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
29+
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
30+
l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
31+
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
32+
l.hash
33+
);
34+
35+
</script>
36+
</head>
37+
<body>
38+
</body>
39+
</html>

src/utils/router.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
export class SimpleRouter {
22
private routes = new Map<string, string>();
33
private currentRoute = '/';
4+
private basePath = '';
45

56
constructor() {
7+
// Set base path in production (for GitHub Pages)
8+
if (import.meta.env.PROD) {
9+
this.basePath = '/toolbox';
10+
}
11+
612
window.addEventListener('popstate', () => this.handleRoute());
713
this.handleRoute();
814
}
@@ -12,12 +18,19 @@ export class SimpleRouter {
1218
}
1319

1420
navigate(path: string) {
15-
window.history.pushState({}, '', path);
21+
const fullPath = this.basePath + path;
22+
window.history.pushState({}, '', fullPath);
1623
this.handleRoute();
1724
}
1825

1926
private handleRoute() {
20-
const path = window.location.pathname;
27+
let path = window.location.pathname;
28+
29+
// Remove base path if present
30+
if (this.basePath && path.startsWith(this.basePath)) {
31+
path = path.slice(this.basePath.length) || '/';
32+
}
33+
2134
this.currentRoute = path;
2235

2336
window.dispatchEvent(

vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import { defineConfig } from "vite";
22

33
export default defineConfig({
44
plugins: [],
5+
base: process.env.NODE_ENV === 'production' ? '/toolbox/' : '/',
56
});

0 commit comments

Comments
 (0)