Refactor site generation into a modular content pipeline
Deploy website / deploy (push) Successful in 24s
Deploy website / deploy (push) Successful in 24s
This commit is contained in:
1 parent
0f69028d4a
commit
146d516f24
20 files changed
+826
-427
No files matched your search
+29
-50
@@ -2,6 +2,8 @@ import fs from 'node:fs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
import { defineConfig } from 'vite'
|
||||
import { createRegenerationQueue } from './dev-reload.js'
|
||||
import { contentDirectories, contentSources, generatedRoot } from './site.config.js'
|
||||
|
||||
function generatedPages(directory) {
|
||||
const inputs = {}
|
||||
@@ -9,10 +11,10 @@ function generatedPages(directory) {
|
||||
const filePath = path.join(directory, entry.name)
|
||||
if (entry.isDirectory()) Object.assign(inputs, generatedPages(filePath))
|
||||
else if (entry.name === 'index.html') {
|
||||
const relativePath = path.relative('.generated', filePath)
|
||||
const relativePath = path.relative(generatedRoot, filePath)
|
||||
const key = relativePath.endsWith('/index.html') ? relativePath.slice(0, -'/index.html'.length) : relativePath
|
||||
inputs[key] = path.resolve(filePath)
|
||||
} else if (entry.name === '404.html' && directory === '.generated') {
|
||||
} else if (entry.name === '404.html' && directory === generatedRoot) {
|
||||
inputs['404'] = path.resolve(filePath)
|
||||
}
|
||||
}
|
||||
@@ -27,7 +29,7 @@ function copyGeneratedFiles() {
|
||||
const dist = path.resolve('dist')
|
||||
if (!fs.existsSync(dist)) return
|
||||
for (const file of files) {
|
||||
const source = path.resolve('.generated', file)
|
||||
const source = path.resolve(generatedRoot, file)
|
||||
if (fs.existsSync(source)) fs.copyFileSync(source, path.join(dist, file))
|
||||
}
|
||||
},
|
||||
@@ -35,57 +37,34 @@ function copyGeneratedFiles() {
|
||||
}
|
||||
|
||||
function contentGenerator() {
|
||||
let timer
|
||||
let generator
|
||||
let rerun = false
|
||||
|
||||
const generate = (server) => {
|
||||
if (generator) {
|
||||
rerun = true
|
||||
return
|
||||
}
|
||||
|
||||
generator = spawn(process.execPath, ['generator.js', '--drafts'], { stdio: 'inherit' })
|
||||
generator.on('error', (error) => {
|
||||
console.error(`Content generation failed: ${error.message}`)
|
||||
})
|
||||
generator.on('close', (code) => {
|
||||
generator = undefined
|
||||
if (code === 0) server.ws.send({ type: 'full-reload' })
|
||||
if (rerun) {
|
||||
rerun = false
|
||||
generate(server)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'content-generator',
|
||||
configureServer(server) {
|
||||
const sources = [
|
||||
path.resolve('notes'),
|
||||
path.resolve('posts'),
|
||||
path.resolve('README.md'),
|
||||
path.resolve('THIRD_PARTY_LICENSES.md'),
|
||||
path.resolve('external.json'),
|
||||
path.resolve('generator.js'),
|
||||
path.resolve('html.js'),
|
||||
]
|
||||
server.watcher.add(sources)
|
||||
const queue = createRegenerationQueue({
|
||||
generate: () => new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, ['generator.js', '--drafts'], { stdio: 'inherit' })
|
||||
child.once('error', reject)
|
||||
child.once('close', (code) => code === 0 ? resolve() : reject(new Error(`exit ${code ?? 'unknown'}`)))
|
||||
}),
|
||||
onSuccess: () => server.ws.send({ type: 'full-reload' }),
|
||||
onError: (error) => {
|
||||
console.error(`Content generation failed: ${error.message}`)
|
||||
server.ws.send({
|
||||
type: 'error',
|
||||
err: { message: `Content generation failed (${error.message}). Check the terminal for details.`, stack: '' },
|
||||
})
|
||||
},
|
||||
})
|
||||
const sources = new Set(contentSources.map((source) => path.resolve(source)))
|
||||
server.watcher.add([...sources])
|
||||
server.httpServer?.once('close', () => queue.close())
|
||||
server.watcher.on('all', (event, file) => {
|
||||
if (!['add', 'change', 'unlink'].includes(event)) return
|
||||
const isMarkdown =
|
||||
file.endsWith('.md') &&
|
||||
(file.startsWith(`${path.resolve('notes')}/`) || file.startsWith(`${path.resolve('posts')}/`))
|
||||
const isExternal = file === path.resolve('external.json')
|
||||
const isStandaloneMarkdown =
|
||||
file === path.resolve('README.md') || file === path.resolve('THIRD_PARTY_LICENSES.md')
|
||||
const isGenerator = file === path.resolve('generator.js') || file === path.resolve('html.js')
|
||||
if (!isMarkdown && !isStandaloneMarkdown && !isExternal && !isGenerator) return
|
||||
clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
generate(server)
|
||||
}, 100)
|
||||
contentDirectories.some((dir) => file.startsWith(`${path.resolve(dir)}/`))
|
||||
if (!isMarkdown && !sources.has(file)) return
|
||||
queue.request()
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -93,7 +72,7 @@ function contentGenerator() {
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [contentGenerator(), copyGeneratedFiles()],
|
||||
root: '.generated',
|
||||
root: generatedRoot,
|
||||
publicDir: '../public',
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -102,14 +81,14 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
watch: {
|
||||
ignored: ['**/.generated/**'],
|
||||
ignored: [`**/${generatedRoot}/**`],
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: path.resolve('dist'),
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
input: generatedPages('.generated'),
|
||||
input: generatedPages(generatedRoot),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user