ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BlockNote 的 Typst 编译器引擎:@blocknote/xl-typst-compiler 在浏览器与 Node 中把 Typst 标记编译为 PDF

BlockNote 的 Typst 编译器引擎:@blocknote/xl-typst-compiler 在浏览器与 Node 中把 Typst 标记编译为 PDF 前端富文本UI组件AI 应用【免费下载链接】BlockNoteA React Rich Text Editor thats block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap.项目地址https://gitcode.com/gh_mirrors/bl/BlockNote点击查看免费下载本指南围绕 packages/xl-typst-compiler/README.md 展开深入讲解 BlockNote 的 PDF 导出引擎 —— 一个把官方typstRust crate 编译为 WebAssembly、并暴露为极简 TypeScript API 的编译器包。文章覆盖其四大设计原则、核心 API 与参数语义、wasm 构建与加载机制并结合仓库内 Rust 绑定、TypeScript 封装与测试用例给出源码级印证。读完你将掌握如何在浏览器或 Node 中独立调用TypstCompiler把 Typst 标记、字体与资源编译为 PDF如何启用原生 PDF/UA-1 无障碍校验以及如何复现其 wasm 产物构建流程。包定位BlockNote PDF 导出的编译内核blocknote/xl-typst-compiler的定位一句话可以概括Typst 标记 资源 字体进PDF 字节或结构化诊断出。它把 Typst 仅导出TypstCompiler、isPdfStandardViolation与若干类型。它是 BlockNote 的 PDF 导出docs 中 Export 功能的引擎但同时完全可以独立使用浏览器与 Node 环境皆可README 明确声明 it also works standalone, in browsers and Node alike。从仓库结构看packages/xl-typst-exporter/package.json 以blocknote/xl-typst-compiler: workspace:^声明了对本包的依赖导出器负责把 BlockNote 文档转换成 Typst 源码而本包负责真正把它编译成 PDF 字节。四大设计原则README 用四个要点概括了本包的工程取舍这些原则直接决定 API 形态值得逐一展开。1. 永远无网络访问wasm 模块内嵌零字体、不下载任何内容文本只使用调用方通过fonts/addFont提供的字体渲染。如果一个文档需要某个缺失的字体编译会大声失败fail loudly而不是用替换字形substituted glyphs悄悄渲染出错误结果。这一点与浏览器中常见的 用系统字体兜底 方案形成鲜明对比。源码侧的证据在 rust/src/lib.rs文件头注释明确列出刻意排除的清单——包注册表BlockNote 生成的标记不 import 任何包、增量渲染、SVG/矢量输出、系统字体访问、任何形式的网络访问。测试 typstCompiler.test.ts 专门验证了这条行为创建一个不加载任何字体的TypstCompiler再编译$x$得到的是error: compile-failed且首个错误消息包含no font could be found。2. 没有单例TypstCompiler没有页面级单例你可以随心所欲创建任意多个编译器实例每个实例只拥有自己的字体集合。wasm 模块本身每页只加载一次The wasm module loads once per page。这条设计在 src/typstCompiler.ts 中有具体实现模块级变量wasmReady缓存initWasm的 Promise首次create()触发加载后续所有create()调用复用同一个已加载的模块而每个实例通过new WasmCompiler()独立构造并把传入的fonts逐个addFont到自己的实例上。Rust 侧同样印证——lib.rs 的TypstCompiler结构体只持有fonts、font_hashes与library三个字段注释明确每个compile_pdf调用都会构建一个全新的 world因此实例可以廉价地持有且不存在页面级单例。3. 原生 PDF 标准支持PDF/UA-1pdfStandard: ua-1会产出通过校验validated、明确声明declared、无障碍accessible的 PDF/UA-1文档。Typst 在编译期执行一致性校验——包括标题结构、替代文本alt text、文档标题等不符合标准的文档会以可识别的诊断失败isPdfStandardViolation绝不会产生虚假的一致性声明。从 lib.rs 可以看到实现路径options.pdf_standard经PdfStandards::new([standard])构造校验器再与tagged默认true、timestamp一起组装成typst_pdf::PdfOptions最终调用typst_pdf::pdf(document, pdf_options)产出 PDF。校验失败会以诊断形式返回而不是抛出异常。测试对此有非常具体的验证typstCompiler.test.ts声明pdfStandard: ua-1的合规文档产物 PDF 同时包含pdfuaid与StructTreeRoot标记以 Not level one非一级标题开头的非合规文档编译失败且所有错误都能被isPdfStandardViolation识别并带有源码字节范围d.range图片缺少 alt textcaption: [No alt]时错误消息包含missing alt text失败后回退到不带pdfStandard的普通编译仍能得到结构树StructTreeRoot存在但不含pdfuaid声明标记的 PDF——即不声称合规但产出可用文档。4. 失败即返回值Failures are valuescompilePdf返回的是结果联合类型result union而不是抛异常。编译错误包括标准违规携带结构化的诊断信息message消息、hints提示列表、range源文件中的字节范围。类型定义见 src/typstCompiler.tsexport type CompilePdfResult | { error?: undefined; pdf: Uint8Array; compileWarnings: TypstDiagnostic[]; } | { error: compile-failed; compileErrors: TypstDiagnostic[]; compileWarnings: TypstDiagnostic[]; };注意只有在调用方犯错如传了非法选项时才会抛出异常。Rust 侧 compile_result 与 TS 侧 compilePdf 都遵循同一契约wasm 层把编译失败作为返回值返回统一负载成败同构仅对调用方错误例如pdfStandard: bogus抛错。TS 封装还额外做了一项不变量检查若出现既产出 PDF 又带错误或PDF 和错误都没有的矛盾结果会抛出TypstCompiler invariant violated异常让编译器自身的误报当场暴露typstCompiler.ts。快速上手最小可用示例README 给出的最小示例已具备完整语义这里保持其可复制性并补充注解import { TypstCompiler } from blocknote/xl-typst-compiler; // 1. 创建编译器实例传入字体字节wasm 模块在首次 create 时加载一次 const compiler await TypstCompiler.create({ fonts: [interBytes] }); // 2. 编译Typst 标记 资源 标准选项 → 结果联合 const result compiler.compilePdf(source, { // 标记中引用的文件键为绝对虚拟路径如 /assets/img.png assets: new Map([[/assets/img.png, pngBytes]]), // 声明并校验 PDF/UA-1 无障碍标准 pdfStandard: ua-1, }); // 3. 失败是值按 error 分支处理而不是 try/catch if (result.error) { console.log(result.diagnostics); // 结构化诊断message / hints / range } else { download(result.pdf); // Uint8Array即 PDF 字节 }关于assets有个细节值得注意以.typ结尾的资源会被注册为可 import 的源文件而非原始字节——这样标记中的#import /lib.typ: ...就能找到它。测试 typstCompiler.test.ts 验证了这一点通过assets传入一个定义#let shout(body) [#upper(body)!]的/lib.typ主标记里#import /lib.typ: shout即可正常调用。实现位于 lib.rspath.ends_with(.typ)的文件会被Source::new注册进sources其余进入files作为原始字节图片等。核心 API 与参数语义TypstCompiler.create / TypstCompilerOptionstype TypstCompilerOptions { wasm?: string | URL | Uint8Array; // wasm 模块来源见下文加载 wasm fonts?: readonly Uint8Array[]; // 字体文件字节TTF/OTF/TTC 的每个 face };wasm省略时从包自身文件加载无 CDN也可传 URL 或模块字节见下文专节。fonts字体按文件内嵌 name table 中的家族名被 Typst 识别数组顺序与文件名不参与选型因此文档中#set text(font: ...)必须精确引用字体自己声明的家族名。fontFamilies()可列出已加载名称用于核对引用了未加载家族时会在结果的compileWarnings中出现unknown font family条目测试见 typstCompiler.test.ts。addFont / fontFamiliesaddFont(data: Uint8Array): number加载一个字体文件字体集合TTC中的每个 face 都会被加入字节级完全相同的重复加载是 no-op返回 0。Rust 侧实现按内容哈希去重lib.rs并最多探测 64 个 face 索引。测试验证重复加载同一个 Inter 字体文件返回 0fontFamilies()返回去重后的[Inter 18pt, Geist Mono]typstCompiler.test.ts。fontFamilies(): string[]已加载字体的家族名列表按加载顺序去重——即 Typst 源码中#set text(font: ...)可以引用的名称。compilePdf / CompilePdfOptions参数类型默认值说明sourcestring必填主 Typst 标记源码内部挂载为虚拟路径/main.typassetsReadonlyMapstring, Uint8Array无标记引用的文件键为绝对虚拟路径如/assets/asset-0.typ后缀成为可 import 的源其余为原始字节pdfStandardua-1 \| a-2b \| a-3b \| (string {})无要强制执行的 PDF 标准ua-1产出经编译期校验的 PDF/UA-1taggedbooleantrue是否输出 tagged无障碍结构树creationTimestampnumber无PDF 创建时间戳Unix 秒UTC传固定值可产出字节级可复现的输出省略则不写时间戳几个关键语义tagged: false会去掉结构树测试验证产物 PDF 不再包含StructTreeRoottypstCompiler.test.ts。而不声明pdfStandard时默认仍输出结构树但不会写入pdfuaid一致性声明——即有结构但不声称合规。creationTimestamp与确定性输出固定时间戳下两次编译产物字节完全相同且 PDF 中写入1970-01-01T00:00:00这类时间creationTimestamp: 0的测试见 typstCompiler.test.ts。这得益于 Rust 侧BnWorld::today返回None——沙箱中不读墙上时钟文档里datetime.today()拿不到日期从而保证输出确定lib.rs。诊断与 isPdfStandardViolationtype TypstDiagnostic { message: string; // 保留 typst 原始文本 hints: string[]; // 提示列表 range?: [number, number]; // 主源码中的字节范围当 span 指向主源码时 };错误与警告由所在列表区分compileErrors/compileWarnings因此诊断对象没有 severity 字段可交叉核对。isPdfStandardViolation(diagnostic)通过正则/^PDF\/[A-Za-z0-9.-](?:, ?PDF\/[A-Za-z0-9.-])* error:/判断一条诊断是否为 PDF 标准一致性违规而非真正的编译错误——Typst 会给这类错误加上校验器前缀如PDF/UA-1 error: missing alt text。这让调用方能够区分文档不符合标准预期可回退与文档本身坏了。另一个值得注意的行为警告会同时出现在成功与失败两个分支。文档可能在遇到硬错误之前先积累警告失败结果也会携带compileWarnings避免调用方丢失上下文测试见 typstCompiler.test.ts。构建 wasm从 rust/ 到 pkg/wasm 产物pkg/由rust/目录构建而来工具链在rust/rust-toolchain.toml中钉死目标为wasm32-unknown-unknown构建工具是 wasm-pack。README 给出的构建命令pnpm exec vp run --filter blocknote/xl-typst-compiler buildRust 侧依赖与发布配置rust/Cargo.toml 的依赖列表揭示了编译管线的构成typst 0.15.1—— 官方编译器核心解析、布局、typst::compilePagedDocumenttypst-pdf 0.15.1—— PDF 后端承载 PDF 标准校验与 tagged 结构树typst-layout 0.15.1——PagedDocument分页文档模型comemo、wasm-bindgen、js-sys、serde、serde-wasm-bindgen—— wasm 胶水与序列化console_error_panic_hook—— 把 Rust panic 打印到控制台便于调试。发布配置针对wasm 是线上交付的 web 资产做了专门的体积优化Cargo.tomlopt-level s体积优先、lto true链接时优化、strip true、codegen-units 1同时因为 wasm-pack 自带的旧版 binaryen 的wasm-opt无法校验新版 rustc 产物显式设置wasm-opt false直接交付 rustc 产物。工具链钉在 rust/rust-toolchain.tomlchannel 1.92.0、targets [wasm32-unknown-unknown]。自给自足的构建脚本 ensure-wasm.mjsscripts/ensure-wasm.mjs 是本包构建任务的第一步见 vite.config.ts 中的command: node scripts/ensure-wasm.mjs tsc vp build其职责是在pkg/缺失或过期时自动从rust/编译它内容哈希保鲜对Cargo.toml、Cargo.lock、rust-toolchain.toml、rust/src下所有文件以及wasm-pack版本计算 SHA-256与pkg/.build-hash比对。新鲜则静默退出过期/缺失才触发构建。npm run build:wasm即node scripts/ensure-wasm.mjs --force可跳过保鲜检查强制重建。工具链自供给本地需要 rustup钉死的工具链与 wasm32 target 会在首次使用时自动安装在 CI/Vercel 上脚本会自行引导 rustupcurl ... sh.rustup.rs | sh因为 GitHub 的 ubuntu-24.04 runner 镜像已不再内置 Rust。缓存策略在 Vercel 上CARGO_HOME、RUSTUP_HOME、CARGO_TARGET_DIR全部被重定向到node_modules/.cache/rust下——Vercel 会在构建之间持久化该目录因此只有全新缓存上的第一次构建才支付完整编译成本后续热构建只需数秒。本地开发机若缺工具链脚本会给出指引并报错退出而不是擅自安装。实际构建命令为 wasm-pack 的build . --target web --release --out-dir ../pkg --out-name blocknote_typst_wasm且 cwd 必须位于 crate 目录rust/因为 rustup 是从工作目录向上查找rust-toolchain.toml的。pkg/属于构建产物不纳入版本库但发布到 npm 的包会包含它——package.json 的files字段列出了dist、types、src、pkg/blocknote_typst_wasm.js、pkg/blocknote_typst_wasm_bg.wasm及其.d.ts外加rust/src、rust/Cargo.toml、rust/Cargo.lock、rust/rust-toolchain.toml、README.md与scripts。加载 wasm 的三种方式默认情况下wasm 模块从包自身文件加载相对 wasm-bindgen 胶水解析——不涉及任何 CDNVite、webpack 等打包器会自动把它作为资源输出。这在 vite.config.ts 有配套设计wasm-bindgen 胶水通过包自引用blocknote/xl-typst-compiler/pkg以文件形式随dist/交付不做打包内联胶水需要相对自身 URL 定位.wasm打包会破坏这一机制因此构建配置把blocknote/与node:开头的依赖全部external。需要控制加载自托管、缓存、Node 环境时向TypstCompiler.create传入wasm选项——可以是URL字符串或URL对象或模块字节Uint8Array。模块只加载一次后续create()调用复用对应源码中的wasmReady缓存。测试用例展示了一个典型场景Node 测试运行器没有相对 URL 加载 wasm的能力因此测试显式读取pkg/blocknote_typst_wasm_bg.wasm的字节并通过wasm: wasmBytes传入typstCompiler.test.ts浏览器路径则由 tests 包的 e2e 套件覆盖经打包器走 URL 加载。在 BlockNote PDF 导出中的角色如前所述blocknote/xl-typst-compiler是 BlockNote PDF 导出的引擎。配合 packages/xl-typst-exporter/src/typstExporter.ts 可以看清上下游衔接导出器把 BlockNote 文档转换为完整 Typst 源码图片等资源通过registerImage/registerImageBytes登记为虚拟路径形如/assets/asset-N并通过assetFilesgetter 暴露虚拟路径 → 字节映射注释明确要求消费者把这些文件映射进编译器的文件系统后再编译typstExporter.ts——映射到本包compilePdf的assets参数即可。测试 typstCompiler.test.ts 甚至直接加载导出器的真实测试文档快照__snapshots__/testDocument.typ与其代码高亮主题在pdfStandard: ua-1下编译出包含pdfuaid标记的合规 PDF——这条端到端用例串起了导出器生成标记 → 编译器产出合规 PDF的完整链路。小结blocknote/xl-typst-compiler把官方 Typst 编译器以 wasm 形态带进了 TypeScript 生态四个设计原则离线可用、无单例、原生 PDF/UA-1、失败即值环环相扣离线与字体自供给保证输出确定性无单例让 API 简单可组合PDF 标准校验与结构化诊断让导出失败成为可编程处理的业务分支而非异常。无论你是想在自己的项目中独立复用它做 Typst→PDF 编译还是深入理解 BlockNote 的 PDF 导出链路都可以从 README.md 出发配合 src/typstCompiler.ts、rust/src/lib.rs 与 scripts/ensure-wasm.mjs 三条主线继续研读。赞分享前端富文本UI组件AI 应用【免费下载链接】BlockNoteA React Rich Text Editor thats block-based (Notion style) and extensible. Built on top of Prosemirror and Tiptap.项目地址https://gitcode.com/gh_mirrors/bl/BlockNote点击查看免费下载相关推荐RoaringBitmap高级特性详解内存映射、64位整数支持与线程安全RoaringBitmap高级特性详解内存映射、64位整数支持与线程安全 RoaringBitmap是一个高效的位图数据结构库特别适合于大数据集下的集合操作后端Pandoc 转 Typst 引文语法映射从 pandoc 引用到 Typst 引用标记Pandoc 转 Typst 引文语法映射从 pandoc 引用到 Typst 引用标记 本文以 pandoc 官方命令行测试 test/command/10文档开发工具CLI编译提速50%Typst多线程优化实战指南编译提速50%Typst多线程优化实战指南 你是否还在忍受单线程编译带来的漫长等待当处理大型文档或复杂排版时Typst默认配置可能无法充分利用多核CPU性编译器CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表