129 lines
6.9 KiB
JavaScript
129 lines
6.9 KiB
JavaScript
import 'dotenv/config';
|
|
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { checkProject, printIssues } from "./check.js";
|
|
import { joinBase, normalizeSiteBase } from "./lib/site-base.js";
|
|
import { auditBuiltDecks } from "./lib/overflow-audit.js";
|
|
import { formatOverflowAuditResult } from "./lib/overflow-report.js";
|
|
import { runSlidev } from "./lib/run-slidev.js";
|
|
import { resolveTheme } from "./lib/theme.js";
|
|
const cwd = process.cwd();
|
|
const siteBase = normalizeSiteBase(process.env.SITE_BASE);
|
|
let config;
|
|
let dist;
|
|
async function main() {
|
|
const result = await checkProject(cwd);
|
|
config = result.config;
|
|
dist = path.join(cwd, config.outDir);
|
|
const { decks, issues } = result;
|
|
printIssues(issues, cwd);
|
|
if (issues.some(issue => issue.level === 'error'))
|
|
throw new Error('内容检查失败,已停止构建');
|
|
const published = decks.filter(deck => !deck.draft);
|
|
await rm(dist, { recursive: true, force: true });
|
|
await mkdir(dist, { recursive: true });
|
|
for (const deck of published)
|
|
await buildDeck(deck);
|
|
await reportSlideOverflow(published);
|
|
await writeFile(path.join(dist, 'index.html'), renderIndex(published), 'utf8');
|
|
await writeFile(path.join(dist, '_headers'), renderHeaders(), 'utf8');
|
|
await writeFile(path.join(dist, '_redirects'), renderRedirects(published), 'utf8');
|
|
console.log(`\n构建完成:${published.length} 套演示 → ${path.relative(cwd, dist)}`);
|
|
}
|
|
async function buildDeck(deck) {
|
|
const output = path.join(dist, deck.slug);
|
|
const base = joinBase(siteBase, deck.slug);
|
|
console.log(`\nBuilding ${deck.slug} (${base})`);
|
|
await runSlidev([
|
|
'build',
|
|
deck.entry,
|
|
'--theme',
|
|
resolveTheme(config.theme),
|
|
'--out',
|
|
output,
|
|
'--base',
|
|
base,
|
|
'--router-mode',
|
|
'hash',
|
|
]);
|
|
const assets = path.join(deck.directory, 'assets');
|
|
await cp(assets, path.join(output, 'assets'), { recursive: true, force: true }).catch(() => undefined);
|
|
await copySpaEntry(output, 'presenter');
|
|
await copySpaEntry(output, 'overview');
|
|
await copySpaEntry(output, 'notes-edit');
|
|
}
|
|
async function copySpaEntry(output, route) {
|
|
const html = await readFile(path.join(output, 'index.html'), 'utf8');
|
|
const routeBootstrap = `<script>if(location.hash!==\"#/${route}\")location.hash=\"#/${route}\"</script>`;
|
|
const routedHtml = html.replace('</head>', `${routeBootstrap}</head>`);
|
|
const directory = path.join(output, route);
|
|
await mkdir(directory, { recursive: true });
|
|
await writeFile(path.join(directory, 'index.html'), routedHtml, 'utf8');
|
|
}
|
|
async function reportSlideOverflow(decks) {
|
|
if (!decks.length)
|
|
return;
|
|
console.log('\n检查编译后的幻灯片内容边界...');
|
|
const result = await auditBuiltDecks({ root: dist, decks, siteBase });
|
|
formatOverflowAuditResult(result, cwd).forEach(line => console.log(line));
|
|
}
|
|
function renderIndex(decks) {
|
|
const cards = decks.map((deck) => {
|
|
const href = joinBase(siteBase, deck.slug);
|
|
const cover = deck.cover
|
|
? `<img src="${escapeAttribute(`${href}${deck.cover.replace(/^\.\//, '')}`)}" alt="" loading="lazy">`
|
|
: `<div class="cover-fallback" aria-hidden="true"><span>${escapeHtml(deck.title.slice(0, 1))}</span></div>`;
|
|
const tags = deck.tags.map(tag => `<li>${escapeHtml(tag)}</li>`).join('');
|
|
return `<article class="deck-card">
|
|
<a class="cover" href="${escapeAttribute(href)}">${cover}</a>
|
|
<div class="deck-copy">
|
|
<p class="meta">${escapeHtml(deck.date || 'Undated')}${deck.author ? ` · ${escapeHtml(deck.author)}` : ''}</p>
|
|
<h2><a href="${escapeAttribute(href)}">${escapeHtml(deck.title)}</a></h2>
|
|
${deck.description ? `<p class="description">${escapeHtml(deck.description)}</p>` : ''}
|
|
${tags ? `<ul class="tags">${tags}</ul>` : ''}
|
|
</div>
|
|
</article>`;
|
|
}).join('\n');
|
|
return `<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<meta name="description" content="${escapeAttribute(config.description)}">
|
|
<title>${escapeHtml(config.title)}</title>
|
|
<style>${landingStyles()}</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<header>
|
|
<p class="eyebrow">MARKDOWN PRESENTATIONS</p>
|
|
<h1>${escapeHtml(config.title)}</h1>
|
|
<p class="lede">${escapeHtml(config.description)}</p>
|
|
</header>
|
|
<section class="deck-list" aria-label="演示列表">
|
|
${cards || '<p>暂无公开演示。</p>'}
|
|
</section>
|
|
</main>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
function landingStyles() {
|
|
return `:root{font-family:Inter,"Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif;color:#222;background:#f6f5f2}*{box-sizing:border-box}body{margin:0}main{width:min(1120px,calc(100% - 40px));margin:auto;padding:72px 0 96px}header{border-bottom:2px solid #1d4ed8;padding-bottom:28px;margin-bottom:36px}.eyebrow{font-size:12px;font-weight:700;letter-spacing:.18em;color:#1d4ed8}h1{font-size:clamp(48px,8vw,82px);line-height:1;margin:14px 0}.lede{font-size:clamp(19px,2.2vw,26px);color:#555;margin:0}.deck-list{display:grid;gap:28px}.deck-card{display:grid;grid-template-columns:minmax(220px,36%) 1fr;background:#fff;border:1px solid #d9d7d2;border-radius:10px;overflow:hidden;box-shadow:0 12px 36px rgba(30,64,175,.08)}.cover{display:block;min-height:230px;background:#eae7e2}.cover img{display:block;width:100%;height:100%;min-height:230px;object-fit:cover}.cover-fallback{height:100%;min-height:230px;display:grid;place-items:center;background:linear-gradient(135deg,#eff6ff,#dbeafe);color:#1d4ed8;font-size:92px;font-weight:700}.deck-copy{padding:32px}.meta{font-size:13px;color:#777;margin:0 0 10px}h2{font-size:clamp(26px,3vw,38px);line-height:1.15;margin:0}h2 a{color:inherit;text-decoration:none}h2 a:hover{color:#1d4ed8}.description{font-size:17px;line-height:1.7;color:#555}.tags{display:flex;gap:8px;flex-wrap:wrap;list-style:none;padding:0;margin:24px 0 0}.tags li{font-size:12px;background:#eff6ff;color:#1d4ed8;padding:5px 9px;border-radius:999px}@media(max-width:680px){main{padding-top:44px}.deck-card{grid-template-columns:1fr}.cover,.cover img,.cover-fallback{min-height:190px}.deck-copy{padding:24px}}`;
|
|
}
|
|
function renderHeaders() {
|
|
return `/*.html\n Cache-Control: public, max-age=0, must-revalidate\n\n/*/assets/*\n Cache-Control: public, max-age=31536000, immutable\n`;
|
|
}
|
|
function renderRedirects(decks) {
|
|
return decks.map(deck => `${joinBase(siteBase, deck.slug)}* ${joinBase(siteBase, deck.slug)}index.html 200`).join('\n');
|
|
}
|
|
function escapeHtml(value) {
|
|
return value.replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character]);
|
|
}
|
|
function escapeAttribute(value) {
|
|
return escapeHtml(value);
|
|
}
|
|
await main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exitCode = 1;
|
|
});
|
|
//# sourceMappingURL=build.js.map
|