快速开始与本地开发
本页介绍如何在本地把 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(本地开发工具链架构)
上图对应关系与设计意图:
- 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 |
| pnpm | 12.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):
1# 安装依赖
2pnpm install
3
4# 启动开发服务器
5pnpm dev
6
7# 类型检查
8pnpm check
9
10# 格式化 + 检查
11pnpm fl
12
13# 构建静态站点
14pnpm build如果启用了 corepack,pnpm 版本会按 packageManager 自动切换,无需手动安装对应版本。
本地开发的核心控制流
从敲下 pnpm dev 到看到页面,实际发生的事情:
需要强调的三个设计决策:
worker: { format: 'es' }不是可选项:vite.config.ts注释明确指出 pdf.js 的 worker 是 ESM-only,默认 IIFE 打包会「静默失败」(vite.config.ts#L26-L27)。也就是说,如果你删掉这一行,本地开发看起来正常,但 PDF 渲染会在运行时挂掉且不报明显错误——这是本地开发最典型的坑。assetsInclude: ['**/*.tar.gz']:仓库中的.tar.gz资源(字体打包产物等)会被 Vite 当作资源文件处理(vite.config.ts#L25)。- 语言路由在构建期生成: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 dev | vite dev | 启动开发服务器(带 HMR) |
pnpm build | node -e "fs.rmSync('build',{recursive:true,force:true})" && vite build | 先强制清空 build/ 目录再构建,避免残留旧产物 |
pnpm preview | vite preview | 本地预览 build/ 静态产物 |
pnpm check | svelte-kit sync && svelte-check --tsconfig ./tsconfig.json | 生成 SvelteKit 类型并运行一次 svelte-check 类型检查 |
pnpm check:watch | 同上 + --watch | 监听模式类型检查 |
pnpm format | prettier --write . | 用 Prettier 格式化整个仓库 |
pnpm lint | prettier --check . && eslint . | CI 风格检查:先 Prettier 再 ESLint |
pnpm fl | prettier --write . && eslint . --fix | 格式化 + ESLint 自动修复(README 推荐日常使用) |
pnpm m | tsx ./scripts/manage-messages.js | 只读模式运行 i18n 文案检查脚本 |
pnpm mw | tsx ./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 模式
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:插件链与构建期全局常量
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_PATH | string(URL 路径段,如 /endfield-docmaker) | ''(根路径部署) | 同时驱动 adapter-static 的 pages/assets 输出目录、kit.paths.base、paths.relative 以及 Paraglide 的 urlPatterns;部署到子路径时必须设置 |
packageManager | string | pnpm@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(常见坑与边界情况)
基于上述源码,本地开发中可预期的失败模式:
- 删掉
worker: { format: 'es' }后 PDF 预览静默失败。源码注释原话:pdf.js's worker is ESM-only; the default IIFE bundle breaks it silently(vite.config.ts#L26-L27)。症状是页面正常但预览区不渲染,且没有清晰报错——排查时应第一时间检查这行配置。 - 在 git 仓库外运行
pnpm dev/pnpm build直接崩溃。execSync('git rev-parse --short HEAD')在非 git 目录会抛异常(vite.config.ts#L11)。如果只下载了源码压缩包,需要先git init或补一次提交。 - 改了字体文件但页面仍用旧字体。
__FONTS_VERSION__在 Vite 启动时计算一次,dev server 不重启不会更新;浏览器端 IndexedDB 缓存也要依赖该指纹失效(vite.config.ts#L14-L22)。 - 子路径部署忘记设置
BASE_PATH。会导致静态资源 404 且英文路由/en/生成错误;该变量需要同时影响 adapter 输出目录与 Paraglide URL 模式(svelte.config.js#L12-L21、vite.config.ts#L35-L43)。 - 手改
src/lib/paraglide/被覆盖。该目录由 Vite 插件生成,正确做法是改messages/*.json或用pnpm mw。 - 用非 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/,属于兄弟页面主题。
Related Links(相关链接)
- README.md — 功能特性、部署入口、项目结构总览
- package.json — 脚本与依赖清单
- svelte.config.js — adapter-static 与 Runes 配置
- vite.config.ts — 插件链与编译期常量
- src/lib/typst.svelte.ts — Typst WASM 初始化与编译逻辑(见 PDF 生成相关页面)
- scripts/manage-messages.ts — i18n 文案管理脚本(见国际化相关页面)