feat(pwa): SW 预缓存自动生成 + Sveltia 瞬间管理(池 4/5)

- gen-sw-precache.mjs post-build:按 dist 产物推导路由重写 dist/sw.js
  预缓存段(AUTO 标记锚定;规则=顶层+/en/ 轨深度≤1、/admin 永不入
  列)——新增顶层页自动进离线包,根治手工清单漏页维护债;实测 19
  路由含 moments 双轨、admin 排除
- build.sh 守卫挂钩(守卫先行纪律);sw.js 源内兜底清单保留
- Sveltia 加 moments 集合:随手发瞬间(text/image 可选+hint 自查条
  件必填、pubDate 日期件、tags、slug 日期模板)——管理后台从只管
  照片扩到内容面第一步

验证: 产物 sw 19 路由断言全过;E2E 33/33 PASS;yaml 解析过;bash -n 过

Spec: none (离线包自动化+后台扩容,车道级交付)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-10-26 20:02:32 +08:00
1 parent 3a59edc79a
commit 3941a34f04
4 files changed
+60

No files matched your search

+1
View File
@@ -28,6 +28,7 @@ pnpm astro build
# 存在性守卫:新文件未推送到 main 前构建仓无此脚本,绝不让发布链因车道在途文件断掉
[ -f scripts/build-perf/subset-font-css.mjs ] && node scripts/build-perf/subset-font-css.mjs dist || echo "[build] subset 脚本未就绪,跳过"
# 首页阻塞 CSS 内联(车道 D):守卫先行同上——脚本未到 main 前绝不炸链
[ -f scripts/build-perf/gen-sw-precache.mjs ] && node scripts/build-perf/gen-sw-precache.mjs dist || echo "[build] sw-precache 生成器未就绪,跳过"
[ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过"
echo "[build] rsync -> $STAGING"
+16
View File
@@ -16,6 +16,22 @@ output:
omit_empty_optional_fields: true
collections:
# 瞬间:随手发(text/image 至少其一——Sveltia 无法表达条件必填,两者都设可选,
# 保存前自查;pubDate 必填)。图片走图库媒体库(content/photos 同源可复用)
- name: moments
label: 瞬间
folder: content/moments
create: true
slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
summary: '{{fields.pubDate}} · {{fields.text}}'
fields:
- { label: 时间, name: pubDate, widget: datetime, date_format: 'YYYY-MM-DD', time_format: false, format: 'YYYY-MM-DD' }
- { label: 文字, name: text, widget: text, required: false, hint: '一句话(与图片至少填一项)' }
- { label: 图片, name: image, widget: image, required: false, hint: '与文字至少填一项;建议先压缩' }
- label: 标签
name: tags
widget: list
required: false
- name: photos
label: 图库照片
folder: content/photos
+3
View File
@@ -10,12 +10,15 @@
const VERSION = 'w4x-v2';
// install 预缓存核心路由(SSG 全量 ~30 页 HTML,<1MB,换来全站离线可读;
// 列表由构建产物路由手工维护,新增顶层页时补一行)
// PRECACHE:AUTO-BEGIN 以下为兜底清单——构建期由 scripts/build-perf/gen-sw-precache.mjs
// 按产物自动重写(规则:顶层+en 轨路由,深度≤1 段;/admin 永不预缓存)
const PRECACHE = [
'/', '/archives/', '/gallery/', '/moments/', '/books/', '/opensource/',
'/about/', '/terminal/',
'/en/', '/en/archives/', '/en/gallery/', '/en/moments/', '/en/books/',
'/en/opensource/', '/en/about/', '/en/terminal/',
];
// PRECACHE:AUTO-END
const IMMUTABLE = [
/\/_astro\//, // 构建产物(文件名带哈希)
/\/fonts?\//, // 字体切片(fontsource,内容不变)
+40
View File
@@ -0,0 +1,40 @@
/**
* SW PRECACHE 自动生成:post-build 按 dist 产物推导路由清单,重写 dist/sw.js
* 兜底段(AUTO-BEGIN/END 标记之间)。规则:顶层路由 + /en/ 轨(路径段 ≤1,
* 排除 /en 自身段计数),排除 /admin(CMS 非离线内容)。新增顶层页自动入列——
* 根治手工清单漏页(Phase 5e 维护债)。
*/
import { readdirSync, readFileSync, writeFileSync, statSync, existsSync } from 'node:fs';
import { join } from 'node:path';
const dist = process.argv[2] ?? 'dist';
const swPath = join(dist, 'sw.js');
const routes = [];
// dist 下所有 index.html → 站点路由(目录名映射)
function walk(dir, prefix) {
for (const name of readdirSync(dir)) {
const full = join(dir, name);
if (!statSync(full).isDirectory()) continue;
if (name === 'admin') continue; // CMS 不进离线包
walk(full, `${prefix}/${name}`);
}
if (prefix && existsSync(join(dir, 'index.html'))) {
const segs = prefix.replace(/^\/en(?=\/|$)/, '').split('/').filter(Boolean);
if (segs.length <= 1) routes.push(prefix.endsWith('/') ? prefix : `${prefix}/`);
}
}
walk(dist, '');
routes.sort();
const sw = readFileSync(swPath, 'utf-8');
const re = /(\/\/ PRECACHE:AUTO-BEGIN[\s\S]*?const PRECACHE = \[)[\s\S]*?(\];\n\/\/ PRECACHE:AUTO-END)/;
if (!re.test(sw)) {
console.error('[gen-sw-precache] sw.js 缺少 AUTO 标记,跳过(保持兜底清单)');
process.exit(0);
}
const list = routes.map((r) => ` '${r}',`).join('\n');
const next = sw.replace(re, `$1\n${list}\n$2`);
writeFileSync(swPath, next);
console.log(`[gen-sw-precache] ${routes.length} 路由入预缓存: ${routes.slice(0, 4).join(' ')}${routes.length > 4 ? ' …' : ''}`);