-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprerender.js
More file actions
123 lines (105 loc) · 4.61 KB
/
Copy pathprerender.js
File metadata and controls
123 lines (105 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const staticRoutes = [
{ path: '/', title: null },
{ path: '/about', title: 'About' },
{ path: '/projects', title: 'Projects' },
{ path: '/gallery', title: 'Gallery' },
{ path: '/now', title: 'Now' },
{ path: '/spotting', title: 'Spotting Stats', description: "Every frame in my planespotting gallery, counted: gear, focal lengths, exposure and time of day, read straight out of the EXIF." },
{ path: '/changelog', title: 'Changelog', description: 'Every commit to leodeng.dev, generated from the repo at build time.' },
{ path: '/homelab', title: 'Homelab', description: 'Live status of my home server: uptime, load, memory and CPU temperature, pushed every minute.' },
{ path: '/tokens', title: 'Token Stats', description: 'How many LLM tokens I have burned through, today and all time, counted from local session logs and pushed from my server.' },
{ path: '/stack', title: 'Stack' },
{ path: '/feed', title: 'RSS Feed' },
{ path: '/blog', title: 'Blog' },
{ path: '/contact', title: 'Contact' },
]
function escapeXml(str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
}
function withMeta(html, { title, description }) {
const fullTitle = title ? `${title} | leodeng.dev` : 'leodeng.dev'
let out = html.replace(/<title>.*?<\/title>/, `<title>${fullTitle}</title>`)
out = out.replace(/(<meta property="og:title"\s+content=").*?(")/, `$1${fullTitle}$2`)
out = out.replace(/(<meta name="twitter:title"\s+content=").*?(")/, `$1${fullTitle}$2`)
if (description) {
out = out.replace(/(<meta name="description" content=").*?(")/, `$1${description}$2`)
out = out.replace(/(<meta property="og:description" content=").*?(")/, `$1${description}$2`)
out = out.replace(/(<meta name="twitter:description" content=").*?(")/, `$1${description}$2`)
}
return out
}
async function prerender() {
const { render, getAllPosts, getPost, collections } = await import('./dist-ssr/entry-server.js')
const templatePath = path.join(__dirname, 'dist/index.html')
const template = fs.readFileSync(templatePath, 'utf-8')
const postRoutes = getAllPosts().map(p => ({
path: `/blog/${p.slug}`,
title: p.title,
description: p.description,
}))
const collectionRoutes = collections.map(c => ({
path: `/gallery/${c.slug}`,
title: c.title,
description: c.description,
}))
const routes = [...staticRoutes, ...postRoutes, ...collectionRoutes]
for (const route of routes) {
const appHtml = await render(route.path)
const page = withMeta(
template.replace('<div id="app"></div>', `<div id="app">${appHtml}</div>`),
route
)
const outPath = route.path === '/'
? templatePath
: path.join(__dirname, 'dist', `${route.path.slice(1)}.html`)
fs.mkdirSync(path.dirname(outPath), { recursive: true })
fs.writeFileSync(outPath, page)
console.log(`✓ Pre-rendered ${route.path}`)
}
const sitemap = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...routes.map(r => ` <url><loc>https://leodeng.dev${r.path}</loc></url>`),
'</urlset>',
'',
].join('\n')
fs.writeFileSync(path.join(__dirname, 'dist/sitemap.xml'), sitemap)
console.log('✓ Generated sitemap.xml')
const rssItems = postRoutes.map(r => {
const post = getPost(r.path.replace('/blog/', ''))
const link = `https://leodeng.dev${r.path}`
return [
' <item>',
` <title>${escapeXml(r.title)}</title>`,
` <link>${link}</link>`,
` <guid>${link}</guid>`,
` <pubDate>${new Date(post.date).toUTCString()}</pubDate>`,
r.description ? ` <description>${escapeXml(r.description)}</description>` : '',
` <content:encoded><![CDATA[${post.html}]]></content:encoded>`,
' </item>',
].filter(Boolean).join('\n')
})
const rss = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">',
' <channel>',
' <title>leodeng.dev</title>',
' <link>https://leodeng.dev/blog</link>',
' <description>Leo Deng\'s blog: self-hosting, IoT, and building things.</description>',
' <language>en</language>',
...rssItems,
' </channel>',
'</rss>',
'',
].join('\n')
fs.writeFileSync(path.join(__dirname, 'dist/rss.xml'), rss)
console.log('✓ Generated rss.xml')
}
prerender().catch(err => {
console.error('Pre-render failed:', err)
process.exit(1)
})