Files
cms-website/themes/pilot-minimal/astro.config.mjs
T
浅念 0a7578b5eb chore(theme): dist 根 stub 清理归属钩子;build.format 显式契约
sf-literal-html-paths 钩子收尾清理 astro 5.18.2 内容层无条件发射的
content-assets.mjs/content-modules.mjs(content/consts.js
ASSET_IMPORTS_FILE——本主题零内容集合,dist 树=target_file 面);
build.format:'directory' 由缺省依赖升为显式契约,*.html.html 残留
自检守护折平前提(偏离即构建明错)。render-cases 钉死 dist 根无 stub。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 10:05:57 +08:00

80 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { existsSync, readdirSync, renameSync, rmSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'astro/config'
// dist 根 stub 清单(astro 5.18.2 content layer 无条件发射的空模块桩——content/consts.js
// 的 ASSET_IMPORTS_FILE='content-assets.mjs' / MODULES_IMPORTS_FILE='content-modules.mjs',
// 静态构建随 .astro/ 汇入 dist 根;本主题零内容集合,产物纯页面/媒体——桩归属主题清理)。
const DIST_ROOT_STUBS = ['content-assets.mjs', 'content-modules.mjs']
// 字面 .html 路径对齐(URL pathname 保真——快线 Task 4 钉死的映射规则之一):
// Astro 页面产物在 build.format 三态下分别是 <path>/index.html(directory——尾斜杠路由
// 的正确形态)或 <path>.html(file/preserve——会给无后缀路由追加后缀)。两者都产不出与
// routes.json targetFile 逐字节一致的 dist/news/x.html 字面文件(file/preserve 会得到
// x.html.html)。此钩子在构建收尾把 .html 后缀路由的包装目录折平为字面文件——
// Task 5 管线的 manifest 对齐(target_file 是唯一权威)因此恒为 no-op。
function literalHtmlPaths() {
return {
name: 'sf-literal-html-paths',
hooks: {
'astro:build:done': async ({ pages, dir, logger }) => {
const dist = fileURLToPath(dir)
let moved = 0
for (const { pathname } of pages) {
// pages 的 pathname 是相对 pageName(directory 格式恒带尾斜杠,如
// 'news/x.html/')——剥尾斜杠后以 .html 后缀判定字面文件路由
const rel = pathname.replace(/\/+$/, '')
if (!rel.endsWith('.html')) continue
const wrapper = join(dist, rel)
const nested = join(wrapper, 'index.html')
if (!existsSync(nested)) continue
const tmp = join(dist, `.sf-literal-${moved}.tmp`)
renameSync(nested, tmp)
rmSync(wrapper, { recursive: true })
renameSync(tmp, wrapper)
moved++
}
if (moved > 0) logger.info(`字面 .html 路径对齐:${moved} 页`)
// format 契约自检:x.html.html 残留只在 format 偏离 directory(file/preserve 给
// .html 路由追加后缀)时出现——折平逻辑的前提破裂即明错,不留错形产物
const doubled = readdirSync(dist, { recursive: true }).filter(
(f) => typeof f === 'string' && f.replaceAll('\\', '/').endsWith('.html.html'),
)
if (doubled.length > 0) {
throw new Error(
`dist 出现 ${doubled.length} 个 *.html.html 残留(如 ${doubled[0]}):build.format 偏离 'directory'——字面 .html 折平前提破裂(Task 5 target_file 对齐将失真)`,
)
}
// dist 根 stub 清理(见 DIST_ROOT_STUBS 注记——内容层桩不是站点产物)
for (const stub of DIST_ROOT_STUBS) {
rmSync(join(dist, stub), { force: true })
}
},
},
}
}
// 纯静态输出(Task 4/5 消费)。页面数据全部来自 SF_BUILD_INPUT 六文件(load-render-data
// 单一入口),不设 site——canonical/og 的 origin 是每站点数据(site.json 的 origin),
// 不是主题级配置。@sf/contracts 直连源码(与根 tsconfig/vitest 同映射——契约包零依赖
// 纯 TS,vite 直接编译内联,无需预构建 dist)。
// build.format='directory' 显式契约(非缺省依赖):尾斜杠路由 → <path>/index.html 是
// target_file 映射规则的半边——上面的 *.html.html 自检是其守卫。
export default defineConfig({
output: 'static',
srcDir: './src',
outDir: './dist',
build: { format: 'directory' },
integrations: [literalHtmlPaths()],
vite: {
resolve: {
alias: {
'@sf/contracts': resolve(import.meta.dirname, '../../packages/contracts/src/index.ts'),
},
},
},
})