Files
twosugar 3a99087698 perf(build): copy-routes 按 nginx 拓扑去重,产物 410M→218M
原脚本对每条路由双写 foo.html + foo/index.html,近半数为字节相同的死文件。
改为按线上 nginx 前缀规则只保留所需形态(/docs·/sdk→扁平 .html,其余→
目录 /index.html)并删除死文件。nginx 解析模拟 938 route、0 MISSING。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-28 16:14:18 +08:00

120 lines
3.9 KiB
TypeScript

/**
* Post-build script: reconcile each route to the ONE file shape the production
* nginx actually serves for its URL prefix — matching the legacy VitePress
* output exactly, with no duplicate files.
*
* Production nginx (websites-nginx, open.longbridge.com, `_release.conf`) routes
* by prefix, and each prefix resolves exactly one shape:
* - /docs, /docs/** (API reference lives under /docs/api/**) → `raw${uri}.html`
* - /sdk, /sdk/** → `raw${uri}.html`
* → FLAT file: `foo.html`
* - everything else (catch-all `location /`): /pricing, /skill, locale roots …
* → `raw$path/index.html`
* → DIRECTORY index: `foo/index.html`
* - trailing slash `/foo/` 302-redirects to `/foo`, and `/en/*` 302-redirects
* to `/*` (en is the root locale), so those never hit a file.
* `_common.conf` is a bare proxy_pass with no try_files fallback — the exact file
* must exist, and only that one is ever read.
*
* Astro `build.format:'file'` emits the flat `foo.html` for every route. This
* script rewrites the catch-all routes to their directory-index shape and drops
* the now-dead sibling in both directions, so the deployed tree carries one file
* per route (≈ half the disk of emitting both) while every URL still resolves.
* It is idempotent and reconciles correctly whether run on fresh Astro output
* or on a tree that already contains both shapes.
*/
import fs from 'fs-extra'
import path from 'path'
import { globSync } from 'glob'
const distDir = path.resolve('dist')
if (!fs.existsSync(distDir)) {
console.warn('[copy-routes] dist/ not found — run astro build first')
process.exit(0)
}
/**
* Which file shape does nginx serve for this route?
* FLAT for /docs** and /sdk** (any locale prefix); DIRECTORY for everything else.
*/
function wantsFlat(route: string): boolean {
const bare = route.replace(/^(zh-CN|zh-HK)\//, '') // strip locale prefix
const seg0 = bare.split('/')[0]
return seg0 === 'docs' || seg0 === 'sdk'
}
// Snapshot the page list up front, then reconcile per unique route.
const htmlFiles = globSync('**/*.html', {
cwd: distDir,
absolute: false,
ignore: ['pagefind/**'],
})
// route → which shapes currently exist on disk
const routes = new Map<string, { flat: boolean; dir: boolean }>()
for (const rel of htmlFiles) {
let route: string
let shape: 'flat' | 'dir'
if (path.basename(rel) === 'index.html') {
const dir = path.dirname(rel)
route = dir === '.' ? '' : dir
shape = 'dir'
} else {
route = rel.slice(0, -'.html'.length)
shape = 'flat'
}
const entry = routes.get(route) ?? { flat: false, dir: false }
entry[shape] = true
routes.set(route, entry)
}
let toDir = 0
let toFlat = 0
let removed = 0
function rmdirIfEmpty(absDir: string): void {
try {
if (fs.readdirSync(absDir).length === 0) fs.rmdirSync(absDir)
} catch {
/* non-empty or gone — leave it */
}
}
for (const [route, has] of routes) {
if (route === '') continue // root index.html — homepage, stays
if (route === '404') continue // conventional flat error page, stays
if (route === 'en' || route.startsWith('en/')) continue // dead redirect target
const flatPath = path.join(distDir, `${route}.html`)
const dirPath = path.join(distDir, route, 'index.html')
if (wantsFlat(route)) {
// Need foo.html; foo/index.html (if any) is dead.
if (!has.flat && has.dir) {
fs.copySync(dirPath, flatPath)
toFlat++
}
if (has.dir) {
fs.removeSync(dirPath)
removed++
rmdirIfEmpty(path.join(distDir, route))
}
} else {
// Need foo/index.html; foo.html (if any) is dead.
if (!has.dir && has.flat) {
fs.copySync(flatPath, dirPath)
toDir++
}
if (has.flat) {
fs.removeSync(flatPath)
removed++
}
}
}
console.log(
`[copy-routes] done — ${toDir} →dir, ${toFlat} →flat, ${removed} dead sibling(s) removed`,
)