feat: website templating system — shared nav/footer partials, build-time HTML assembly
All checks were successful
Deploy to Production / Deploy to Server (push) Successful in 1m55s
All checks were successful
Deploy to Production / Deploy to Server (push) Successful in 1m55s
- Build script: scripts/build-pages.js (zero dependencies, Node.js only) - Shared partials: nav.html, footer.html (single source of truth) - 5 page templates in templates/pages/ - Output is byte-for-byte identical to current production - Run: npm run build:pages - Also fixes JSON-LD: 2,500 → 5,000 PDFs/month (was inconsistent)
This commit is contained in:
parent
e51e65524a
commit
a01fbb0357
9 changed files with 1253 additions and 2 deletions
75
scripts/build-pages.js
Normal file
75
scripts/build-pages.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Build-time HTML templating system for DocFast.
|
||||
* No dependencies — uses only Node.js built-ins.
|
||||
*
|
||||
* - Reads page sources from templates/pages/*.html
|
||||
* - Reads partials from templates/partials/*.html
|
||||
* - Replaces {{> partial_name}} with partial content
|
||||
* - Supports <!-- key: value --> metadata comments at top of page files
|
||||
* - Replaces {{key}} variables with extracted metadata
|
||||
* - Writes output to public/
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const ROOT = join(__dirname, '..');
|
||||
const PAGES_DIR = join(ROOT, 'templates', 'pages');
|
||||
const PARTIALS_DIR = join(ROOT, 'templates', 'partials');
|
||||
const OUTPUT_DIR = join(ROOT, 'public');
|
||||
|
||||
// Load all partials
|
||||
const partials = {};
|
||||
for (const file of readdirSync(PARTIALS_DIR)) {
|
||||
if (!file.endsWith('.html')) continue;
|
||||
const name = file.replace('.html', '');
|
||||
partials[name] = readFileSync(join(PARTIALS_DIR, file), 'utf-8');
|
||||
}
|
||||
|
||||
console.log(`Loaded ${Object.keys(partials).length} partials: ${Object.keys(partials).join(', ')}`);
|
||||
|
||||
// Process each page
|
||||
const pages = readdirSync(PAGES_DIR).filter(f => f.endsWith('.html'));
|
||||
console.log(`Processing ${pages.length} pages...`);
|
||||
|
||||
for (const file of pages) {
|
||||
let content = readFileSync(join(PAGES_DIR, file), 'utf-8');
|
||||
|
||||
// Extract all <!-- key: value --> metadata comments from the top
|
||||
const vars = {};
|
||||
while (true) {
|
||||
const m = content.match(/^<!--\s*([a-zA-Z_-]+):\s*(.+?)\s*-->\n?/);
|
||||
if (!m) break;
|
||||
vars[m[1]] = m[2];
|
||||
content = content.slice(m[0].length);
|
||||
}
|
||||
|
||||
// Replace {{> partial_name}} with partial content (support nested partials)
|
||||
let maxDepth = 5;
|
||||
while (maxDepth-- > 0 && content.includes('{{>')) {
|
||||
content = content.replace(/\{\{>\s*([a-zA-Z0-9_-]+)\s*\}\}/g, (match, name) => {
|
||||
if (!(name in partials)) {
|
||||
console.warn(` Warning: partial "${name}" not found in ${file}`);
|
||||
return match;
|
||||
}
|
||||
return partials[name];
|
||||
});
|
||||
}
|
||||
|
||||
// Replace {{variable}} with extracted metadata
|
||||
content = content.replace(/\{\{([a-zA-Z_-]+)\}\}/g, (match, key) => {
|
||||
if (key in vars) return vars[key];
|
||||
console.warn(` Warning: variable "${key}" not defined in ${file}`);
|
||||
return match;
|
||||
});
|
||||
|
||||
// Write output
|
||||
const outPath = join(OUTPUT_DIR, file);
|
||||
writeFileSync(outPath, content);
|
||||
console.log(` ✓ ${file} (${(content.length / 1024).toFixed(1)}KB)`);
|
||||
}
|
||||
|
||||
console.log('Done!');
|
||||
Loading…
Add table
Add a link
Reference in a new issue