Repository Wiki
Naptie/endfield-docmaker

快速开始与本地开发

本页介绍如何在本地把 endfield-docmaker(终末地文档生成器)跑起来:环境要求、依赖安装、开发服务器、常用脚本命令、构建与静态部署路径配置,以及构建期注入的全局常量。endfield-docmaker 是一个纯前端 SvelteKit 应用,通过 WebAssembly 在浏览器中运行 Typst 排版引擎,实时生成《明日方舟:终末地》世界观风格的 PDF 文档,无需任何后端服务。

Purpose and Scope(目的与范围)

本页覆盖「从克隆仓库到本地运行与构建」的完整流程,以及支撑该流程的工程配置文件:

  • 环境要求(Node.js、pnpm 版本约束)
  • 依赖安装与 pnpm dev 开发服务器
  • package.json 中全部脚本命令的语义与底层实现
  • svelte.config.js(adapter-static、Runes 模式、BASE_PATH)与 vite.config.ts(插件链、构建期全局常量)的逐段解析
  • 构建产物形态与部署到子路径的方式
  • 本地开发中的常见坑位与排查方式

以下主题有意留给兄弟页面,本页只在必要处给出指引:

  • 表单 ↔ Typst 编译 ↔ PDF 预览的运行时数据流:见「PDF 生成与 Typst 集成」相关页面
  • 模板体系(红头公文 official-doc.typ、公章 tuzhang.typ、试卷模板):见模板相关页面
  • i18n 文案管理与 scripts/manage-messages.ts 的完整用法:见国际化相关页面
  • 字体资源的 IndexedDB 缓存机制:见字体与资源缓存相关页面

Overview(概述)

endfield-docmaker 的核心卖点是「浏览器内实时出 PDF」:用户在表单中填写参数,前端调用 Typst 的 WebAssembly 编译器(@myriaddreamin/typst.ts + @myriaddreamin/typst-ts-web-compiler / typst-ts-renderer),把模板渲染成 PDF,支持实时预览、新标签页打开与下载。官方部署了三个静态站点入口(GitHub Pages、Cloudflare、Vercel),说明工程对「纯静态 + 可选子路径部署」有明确的一等支持(README.md#L34-L38)。

因此本地开发的目标很明确:用 Vite 起 dev server,所有重活(Typst 编译、字体加载、PDF 渲染)都发生在浏览器里。仓库根目录非常干净,只有少量配置文件(package.json、svelte.config.js、vite.config.ts、tsconfig.json、eslint.config.js、components.json、pnpm-workspace.yaml),没有后端代码、没有 CI 强依赖的服务。

README 的「功能特性」清单进一步说明了本地开发需要关注的边界(README.md#L20-L32):

  • 在浏览器中实时生成 PDF 文件,无需后端服务
  • 多模板 + 每模板若干自定义参数
  • 试卷模板自动计算板块/整卷总分;公文模板生成带随机偏移与旋转的圆形公章
  • 表单内容自动持久化至 localStorage;字体文件自动缓存至 IndexedDB
  • 明暗主题切换、中英双语界面

Architecture(本地开发工具链架构)

Loading diagram...

上图对应关系与设计意图:

  • pnpm 作为唯一包管理器:package.json 声明 "packageManager": "pnpm@12.3.4"(package.json#L65),配合 corepack 可保证团队安装的依赖版本一致;仓库同时存在 pnpm-workspace.yaml 与 pnpm-lock.yaml,锁文件保证复现性。
  • Vite 是唯一的开发/构建入口:pnpm dev 实际执行 vite dev(package.json#L6-L18),SvelteKit 以 Vite 插件形式接入(sveltekit()),而不是独立的 CLI 链。
  • 三个 Vite 插件按顺序组合(vite.config.ts#L28-L45):Tailwind CSS 4 的 Vite 插件负责样式编译;SvelteKit 插件负责路由与 Svelte 5 编译;Paraglide 插件负责把 messages/ 下的翻译文件编译到 src/lib/paraglide/,并把语言路由策略(url → cookie → baseLocale)织入页面。
  • Svelte 5 Runes 模式被强制开启,但对 node_modules 内的第三方组件豁免(svelte.config.js#L6-L10),避免旧式库被错误编译。
  • PDF 生成完全在客户端:routes/+page.svelte 依赖 @myriaddreamin/typst.ts 系列包在浏览器完成编译渲染,本地开发时无需任何 Typst CLI 或后端。

环境要求与依赖安装

前置条件(均由仓库文件直接约束):

依赖要求依据
Node.js需支持 Vite 8 / SvelteKit 2 / TypeScript 6 工具链(建议使用当前 LTS)package.json#L19-L51
pnpm12.3.4(packageManager 字段精确锁定)package.json#L65
git构建期需要 git rev-parse --short HEAD 获取提交哈希vite.config.ts#L11
包下载需能访问 npm registry;首次构建会在浏览器拉取 Typst WASM 产物package.json#L56-L58

安装与启动(README.md#L91-L108):

bash
1# 安装依赖 2pnpm install 3 4# 启动开发服务器 5pnpm dev 6 7# 类型检查 8pnpm check 9 10# 格式化 + 检查 11pnpm fl 12 13# 构建静态站点 14pnpm build

如果启用了 corepack,pnpm 版本会按 packageManager 自动切换,无需手动安装对应版本。

本地开发的核心控制流

从敲下 pnpm dev 到看到页面,实际发生的事情:

Loading diagram...

需要强调的三个设计决策:

  1. worker: { format: 'es' } 不是可选项:vite.config.ts 注释明确指出 pdf.js 的 worker 是 ESM-only,默认 IIFE 打包会「静默失败」(vite.config.ts#L26-L27)。也就是说,如果你删掉这一行,本地开发看起来正常,但 PDF 渲染会在运行时挂掉且不报明显错误——这是本地开发最典型的坑。
  2. assetsInclude: ['**/*.tar.gz']:仓库中的 .tar.gz 资源(字体打包产物等)会被 Vite 当作资源文件处理(vite.config.ts#L25)。
  3. 语言路由在构建期生成:Paraglide 的 urlPatterns 把 en 放在 /:basePath/en/...,zh 作为 baseLocale 落在根路径(vite.config.ts#L35-L43)。本地开发访问 http://localhost:5173/ 得到中文界面,访问 /en/ 得到英文界面。

脚本命令参考(package.json scripts)

package.json 定义的全部脚本(package.json#L6-L18):

命令实际执行作用
pnpm devvite dev启动开发服务器(带 HMR)
pnpm buildnode -e "fs.rmSync('build',{recursive:true,force:true})" && vite build先强制清空 build/ 目录再构建,避免残留旧产物
pnpm previewvite preview本地预览 build/ 静态产物
pnpm checksvelte-kit sync && svelte-check --tsconfig ./tsconfig.json生成 SvelteKit 类型并运行一次 svelte-check 类型检查
pnpm check:watch同上 + --watch监听模式类型检查
pnpm formatprettier --write .用 Prettier 格式化整个仓库
pnpm lintprettier --check . && eslint .CI 风格检查:先 Prettier 再 ESLint
pnpm flprettier --write . && eslint . --fix格式化 + ESLint 自动修复(README 推荐日常使用)
pnpm mtsx ./scripts/manage-messages.js只读模式运行 i18n 文案检查脚本
pnpm mwtsx ./scripts/manage-messages.js --write && prettier --write ./messages写回 messages/ 并格式化

build 脚本值得单独说明:它用 fs.rmSync('build', { recursive: true, force: true }) 先删除构建目录再交给 Vite(package.json#L8)。这是刻意为之——静态站点部署到子路径时(见下节),任何残留的旧 HTML 都可能因为 BASE_PATH 变化而指向错误资源,先删后建能保证产物一致性。

构建期配置与部署路径

svelte.config.js:静态适配 + Runes 模式

javascript
1import adapter from '@sveltejs/adapter-static'; 2 3const basePath = process.env.BASE_PATH ?? ''; 4 5/** @type {import('@sveltejs/kit').Config} */ 6const config = { 7 compilerOptions: { 8 // Force runes mode for the project, except for libraries. Can be removed in svelte 6. 9 runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true) 10 }, 11 kit: { 12 adapter: adapter( 13 basePath 14 ? { 15 pages: `build${basePath}`, 16 assets: `build${basePath}` 17 } 18 : {} 19 ), 20 paths: { base: basePath, relative: !basePath } 21 } 22}; 23 24export default config;

Source: svelte.config.js

三个关键点:

  • BASE_PATH 环境变量驱动子路径部署。仓库同时服务三个官方站点:根路径部署的 https://endfield-docmaker.phi.zone/,以及子路径部署的 https://naptie.github.io/endfield-docmaker/ 与 https://vercel/.../docmaker/(README.md#L36-L38)。设置 BASE_PATH=/endfield-docmaker 时,pages 与 assets 会输出到 build/endfield-docmaker/,并且 paths.relative 翻转为 false(子路径部署下相对链接不可靠,必须用带 base 的绝对链接)。不设置时走空配置,输出到 build/ 根。
  • Runes 模式全局强制、对第三方库豁免。runes: ({ filename }) => ... 以回调形式判断文件路径,node_modules 下的文件返回 undefined(跟随各自库的默认),其余一律 true(svelte.config.js#L8-L9)。注释指出这一段「在 Svelte 6 中可移除」——即它是为了在 Svelte 5 阶段提前统一到响应式 API 而加的过渡开关。这也意味着给本项目写组件必须使用 Runes 语法($state / $derived 等)。
  • @sveltejs/adapter-static 是纯静态输出:没有服务器运行时代码,所有页面预渲染为静态 HTML。这与「无需后端服务」的产品定位一致。

vite.config.ts:插件链与构建期全局常量

typescript
1const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')); 2const commitHash = execSync('git rev-parse --short HEAD').toString().trim(); 3const basePath = process.env.BASE_PATH ?? ''; 4 5// Compute a hash of the fonts directory for cache invalidation. 6const fontsDir = join('src', 'lib', 'assets', 'fonts'); 7const fontFiles = readdirSync(fontsDir).sort(); 8const fontsHash = createHash('sha256'); 9for (const file of fontFiles) { 10 fontsHash.update(file); 11 fontsHash.update(readFileSync(join(fontsDir, file))); 12} 13const fontsVersion = fontsHash.digest('hex').slice(0, 12); 14 15export default defineConfig({ 16 assetsInclude: ['**/*.tar.gz'], 17 // pdf.js's worker is ESM-only; the default IIFE bundle breaks it silently. 18 worker: { format: 'es' }, 19 plugins: [ 20 tailwindcss(), 21 sveltekit(), 22 paraglideVitePlugin({ 23 project: './project.inlang', 24 outdir: './src/lib/paraglide', 25 strategy: ['url', 'cookie', 'baseLocale'], 26 urlPatterns: [ 27 { 28 pattern: `:protocol://:domain(.*)::port?${basePath}/:path(.*)?`, 29 localized: [ 30 ['en', `:protocol://:domain(.*)::port?${basePath}/en/:path(.*)?`], 31 ['zh', `:protocol://:domain(.*)::port?${basePath}/:path(.*)?`] 32 ] 33 } 34 ] 35 }) 36 ], 37 define: { 38 __APP_VERSION__: JSON.stringify(pkg.version), 39 __COMMIT_HASH__: JSON.stringify(commitHash), 40 __FONTS_VERSION__: JSON.stringify(fontsVersion) 41 } 42});

Source: vite.config.ts

  • fontsVersion 是字体缓存失效机制:构建时读取 src/lib/assets/fonts 下所有文件(按名排序保证哈希稳定),用 SHA-256 生成 12 位十六进制摘要。字体一旦变化,__FONTS_VERSION__ 变化,浏览器侧据此判断 IndexedDB 中的字体缓存是否需要刷新。本地增删字体文件后必须重启 dev server 才会重新计算该常量。
  • __APP_VERSION__ / __COMMIT_HASH__ 分别取自 package.json 的 version(当前 0.1.2)与 git rev-parse --short HEAD。注意后者意味着仓库必须处于 git 工作区内,否则 execSync 会抛错导致 dev/build 直接失败。
  • Paraglide 的 urlPatterns 也吃 basePath:这意味着 i18n 语言 URL 与静态部署子路径必须在同一处(BASE_PATH)对齐,部署到子路径时英文站点是 /{basePath}/en/...。

项目结构速查

本地开发时最常打交道的目录(README.md#L53-L89):

1endfield-docmaker/ 2├── messages/ # en.json / zh.json 本地化文本 3├── scripts/manage-messages.ts # i18n 文案管理脚本 4└── src/ 5 ├── routes/ 6 │ ├── +layout.svelte # 根布局 7 │ ├── +layout.ts # 根布局加载逻辑 8 │ ├── +page.svelte # 主页面(表单 + PDF 预览) 9 │ └── layout.css # 全局样式与主题变量 10 └── lib/ 11 ├── constants.ts # 常量定义 12 ├── index.ts # 公共导出 13 ├── tint.ts # 颜色处理 14 ├── typst.svelte.ts # Typst WASM 初始化与编译逻辑 15 ├── utils.ts # 工具函数 16 ├── hooks/ # 业务 hooks 17 ├── assets/ # fonts/ logos/ typst/(模板源) 18 ├── components/ # DateInput、Footer、LocaleSwitch、ThemeToggle、ui/ 19 └── paraglide/ # i18n 生成文件(勿手改,由插件生成)

注意 src/lib/paraglide/ 是 paraglideVitePlugin 的生成目录(vite.config.ts#L33),不要手工编辑;改文案应编辑 messages/*.json(或使用 pnpm mw 脚本同步)。

Configuration Options(环境变量与开关)

变量 / 开关类型默认说明
BASE_PATHstring(URL 路径段,如 /endfield-docmaker)''(根路径部署)同时驱动 adapter-static 的 pages/assets 输出目录、kit.paths.base、paths.relative 以及 Paraglide 的 urlPatterns;部署到子路径时必须设置
packageManagerstringpnpm@12.3.4通过 corepack 精确锁定 pnpm 版本(非环境变量,属于包管理约定)
__APP_VERSION__编译期常量取 package.json version由 Vite define 注入,供 UI 显示版本号
__COMMIT_HASH__编译期常量git rev-parse --short HEAD由 Vite define 注入;依赖本地 git 仓库
__FONTS_VERSION__编译期常量字体目录 SHA-256 前 12 位字体资源指纹,用于浏览器端缓存失效

Failure Modes, Edge Cases & Concurrency(常见坑与边界情况)

基于上述源码,本地开发中可预期的失败模式:

  1. 删掉 worker: { format: 'es' } 后 PDF 预览静默失败。源码注释原话:pdf.js's worker is ESM-only; the default IIFE bundle breaks it silently(vite.config.ts#L26-L27)。症状是页面正常但预览区不渲染,且没有清晰报错——排查时应第一时间检查这行配置。
  2. 在 git 仓库外运行 pnpm dev / pnpm build 直接崩溃。execSync('git rev-parse --short HEAD') 在非 git 目录会抛异常(vite.config.ts#L11)。如果只下载了源码压缩包,需要先 git init 或补一次提交。
  3. 改了字体文件但页面仍用旧字体。__FONTS_VERSION__ 在 Vite 启动时计算一次,dev server 不重启不会更新;浏览器端 IndexedDB 缓存也要依赖该指纹失效(vite.config.ts#L14-L22)。
  4. 子路径部署忘记设置 BASE_PATH。会导致静态资源 404 且英文路由 /en/ 生成错误;该变量需要同时影响 adapter 输出目录与 Paraglide URL 模式(svelte.config.js#L12-L21、vite.config.ts#L35-L43)。
  5. 手改 src/lib/paraglide/ 被覆盖。该目录由 Vite 插件生成,正确做法是改 messages/*.json 或用 pnpm mw。
  6. 用非 Runes 语法写组件导致编译行为不一致。工程对非 node_modules 文件强制 runes: true(svelte.config.js#L8-L9),新组件应使用 Svelte 5 响应式语法。

Performance & Operational Notes(性能与运维说明)

  • 构建即清理:pnpm build 先 rmSync('build') 再构建(package.json#L8),运维侧不需要额外的清理步骤。
  • 依赖体量集中在 dependencies:Typst WASM 三件套(@myriaddreamin/typst.ts、typst-ts-web-compiler、typst-ts-renderer)、pdfjs-dist、@fontsource-variable/* 字体包都属于运行时依赖(package.json#L52-L64),首次 pnpm install 体积可观,但它们只在浏览器使用,不增加服务器成本。
  • 代码质量流水线:pnpm lint = Prettier 检查 + ESLint(package.json#L14),pnpm fl 为本地一键修复;类型层面用 svelte-check 而非 tsc(package.json#L11)。提交前建议跑 pnpm fl 与 pnpm check。
  • 预览产物:pnpm preview(vite preview)可在本地验证 BASE_PATH 设置是否正确,比直接部署更稳妥。

Extension Points(扩展入口)

  • 新增静态资源类型:在 vite.config.ts 的 assetsInclude 中追加扩展名(当前只有 **/*.tar.gz)。
  • 新增语言:修改 Paraglide 的 strategy 与 urlPatterns(vite.config.ts#L34-L43),并在 messages/ 增加对应 JSON 文件。
  • 新增部署子路径:只需设置 BASE_PATH,无需改动代码——配置的耦合点已被收敛到该单一环境变量。
  • 新增模板 / 业务 hook / UI 组件:分别落在 src/lib/assets/typst/、src/lib/hooks/、src/lib/components/,属于兄弟页面主题。