那行提示如果出现在你屏幕上,第一反应不应该是急着关掉,而是把它当作一次安全警示的入口——尤其是你处理的是 Typst 这种“源代码即文档”的排版系统时。程序员圈子里对 Typst 的普遍印象是“比 LaTeX 好上手、比 Word 靠谱”,但很少有人把 .typ 当作一种有格式、有边界、有权限语义的源文件来学习。这恰好意味着,多数人在第一次接触 Typst 项目时,会把源文件当成普通文本去读,忽略了它内部也存在模块引用、本地资源读取、包解析这些需要安全边界的行为。
这轮总结里我不打算只列语法关键词,而是从一份 .typ 文件从创建、编写、模块拆分、编译输出到最终预览的完整链路入手,把 Typst 的源文件格式彻底讲清楚。内容同时兼顾三件事:源文件本身的语法分层、项目里多文件协作时的设计思路,以及预览工具弹“不受信任目录”警告时背后到底在拦什么。无论你是刚开始用 Typst 写简历和论文,还是已经在团队里试着拿它做标准化文档流水线,这篇文章应该都能帮你把“文件格式”四个字从表层的后缀名认知,落到真正可执行的工程理解上。
1. “未授信目录”的提示,恰好引出 Typst 源文件格式的核心话题
1.1 从 .typ 后缀开始,区分“格式”与“标记”
先解决一个最容易混淆的概念:Typst 源文件的后缀是 .typ,本质上是一个 UTF-8 编码的纯文本文件。所谓“文件格式”,并不是指二进制层面有某种专有的编码规则,而是指 Typst 编译器会按照一套特定语法去解析这个文本文件,并把解析结果渲染成带有页面模型的内容。
当你的浏览器或在线文件预览器弹出“预览源文件来自未授信的目录,请停止访问!”,说明预览端已经不把 .typ 当纯文本,而是把它当作“可执行的源文件”在对待。这类拦截在很多文档工具链里都有,比如某些在线 Office 组件遇到从缓存目录、临时上传目录或浏览器隔离目录里加载的文档时,会直接拒绝预览。和 Typst 放一起理解就特别合适:.typ 文件虽然打开后是一行一行的文字,但它内部可能有 #import、#include、#image()、#embed 这类指令,会让编译器去读取同一目录、上级目录甚至网络位置上的其他文件。如果某个工具只把源文件丢进沙箱,却没有把整个项目目录一并挂载进去,那么预览行为就等同于“在未完全信任的环境里执行代码”。
很多人在这一步会直接联想到病毒或恶意脚本。其实更准确的说法是:预览工具无法判断这个目录里的 .typ 文件是否经过项目所有者确认,所以它选择保守策略,也就是不加载不被信任的路径。如果你自己编写 Typst 文件,使用 typst compile 在本地终端中编译,通常不会遇到这种阻断,因为终端进程被赋予的权限来自用户显式发起的命令。
1.2 Typst 源文件为什么需要特别关注权限模型的边界
我可以说一个亲测的场景:做一个包含模板函数的内部工具库时,我习惯把公共样式写在 assets/theme.typ 里,然后在不同子项目的 main.typ 中 #import "../assets/theme.typ": *。这种跨目录引用在命令行下没有任何问题,因为本地文件系统的路径解析由操作系统处理,Typst 进程能直接访问。
但如果是通过部署在服务器上的预览工具加载,事情就完全变了。服务器端能读到的路径仅限于它配置好的目录映射。当用户上传一个 main.typ,它被暂时存放在某个临时文件夹里,此时预览程序如果还想继续解析 ./assets/theme.typ,就必须有能力在那个临时文件夹的上级路径中找到真实项目文件。如果找不到,这个源文件就被判定为“孤立文件”。
那种情况下,工具给出的提示通常就是“预览源文件来自未授信的目录”。换句话说,它警告的不是“Typst 文件有病毒”,而是“当前项目的文件引用链不完整,我们无法保证这个源文件需要的那套目录结构是安全的”。
通过这个视角再去看 Typst 源文件的格式,就应该形成一种习惯:.typ 文件不是一个单一文档,它是一份项目清单。只要涉及多个模块、多个资源文件,就必须把“源文件所在目录”作为基本信任边界来设计。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. .typ 文件的文本底层:哪些命令定义了文件的有效结构
2.1 基础标记层的语法视图
在完全不引入自定义函数和复杂脚本前,Typst 源文件的可读性相当高。它用等号表示章标题,用两个等号表示节,用三个等号表示小节。这和 Markdown 的思路有些形似,但绝不能照搬 Markdown 的习惯去写,因为 Typst 对段落的处理、符号的语义、空行的意义都有自己的一套。
一个最基础的 .typ 文件可以是这样的:
typst复制// 注释用两个斜杠
= 一级标题
== 二级标题
这是正文的第一段。
这是同一段内继续输入的内容。
这是第二段。注意只有空行才能分段,单纯换行不会产生新段落。
文本层面有几个关键规则:
- 单纯的回车不会分段,必须有一个空行把两个文字块隔开。
- 星号
*包起来表示粗体,下划线_包起来表示斜体,反引号`包起来表示等宽代码。 - 无序列表在行首使用
-,有序列表在行首使用+。如果列表项内容需要跨行,缩进必须保持一致。 - 缩进本身并不像 Python 那样产生代码块,它只是帮助解析器识别嵌套结构。
文件里如果混入了 Markdown 的 # 一级标题写法,它并不会成为标题,因为 Typst 的 # 是特殊前缀,用来调用表达式或命令。这一点在迁移旧文档时尤其容易踩坑。
2.2 紧跟其后的“内容模式”与“代码模式”
Typst 源文件里存在一种相当典型的模式切换:一种是写作时直接输入的内容模式,另一种是通过 # 进入的代码模式。任何以 # 开头的标记都表示后面有一个表达式,这个表达式可以是函数调用、变量名或者一段括号表达式。
比如:
typst复制#set page(paper: "a4", margin: 2cm)
#align(center)[
= 文档标题
]
#text(size: 20pt)[正文内容]
常见的理解难点在于:#set、#let、#show 这些都是“顶层命令”,它们可以改变页面格式、定义变量、修改元素渲染规则。而方括号跟在函数名后面,是向函数传递“内容块”。grid、table、figure、image、link 这类排版函数遵循同一套调用逻辑,区别只在参数不同。
文件的有效性在很大程度取决于方括号和圆括号是否配对,以及引号是否闭合。Typst 源文件本身是分段解析的,如果某一段内括号不闭合,编译器会把错误一直报到文件末尾,所以定位错误时不要只盯着报错行,很可能问题出在几十行之前的一个未闭合方括号上。
2.3 内容块、表达式与变量的边界
如果你想在 Typst 源文件里让代码更加结构化,可以使用 #let 定义变量和函数。这就跨入了“源文件不只是文档,更是一个小型编程工程”的领域。
下面这个例子展示了函数定义和内容块传递的常见配合:
typst复制#let section-title(body) = {
set text(15pt, weight: "bold", fill: rgb("#1F6FEB"))
block(width: 100%, body)
}
= 项目概述
#section-title[第一个小节标题]
这里的 body 是一个参数名,赋给它的是方括号里传入的内容块。在函数体内,这个内容块可以直接出现在布局流程中,也可以被继续包装。你可以把这类函数理解成一组“源文件内可复用的格式组件”,它们和外部资源一样,会影响最终渲染结果。如果这类函数被拆到独立的 .typ 文件中,再通过 #import 引入,那么源文件的完整性就变得更加依赖文件之间的相对路径结构。
3. 源文件生成文档的完整链条:编写、编译、预览与格式转换
3.1 本地命令行中的标准编译流程
Typst 官方提供了一个编译器,安装后可直接使用命令行。一个最普通的源文件,输入以下命令即可生成 PDF:
bash复制typst compile main.typ
默认在同目录下生成 main.pdf。如果希望输出到指定目录,可以写成:
bash复制typst compile main.typ output/document.pdf
对于大项目,建议保留一个 main.typ 作为入口文件,其他模块用 #import 引入。编译时只需要针对入口文件操作,编译器会顺着引用关系自动读取全部依赖。
开发期间的实时预览使用 watch 模式:
bash复制typst watch main.typ
这个命令会监听 main.typ 及其所有依赖文件的变化,只要保存文件,就自动触发一次重新编译。实践中的体验是,一次编译耗时常常在几十到几百毫秒,哪怕文档超过几十页也远快于 LaTeX 的完整编译。
3.2 字体、图片与外部资源的加载顺序
对于纯文字排版,源文件只需要依赖编译器内置字体配置。一旦涉及公司品牌字体、自定义字体文件、图片素材,就应该有意识地把资源文件组织在项目内部。
Typst 查找图片时支持相对路径,也支持绝对路径。实际工程中,我更推荐相对路径,因为这样可以保证整个目录结构被移动或打包后仍然能够编译。图片如果放在 assets/images/ 下,在源文件中可以这样写:
typst复制#image("assets/images/banner.png", width: 80%)
注意,这里传入的字符串并不是 Markdown 里的链接格式。字符串使用双引号包裹,路径分隔符为斜杠 /。如果路径包含空格或中文,通常也能直接解析,但最稳妥的做法是在项目根目录下统一命名规范,比如全部使用小写字母、连字符连接,避免不同操作系统对大小写和特殊字符的处理差异导致找不到文件。
字体方面,如果使用本地自定义字体,可以在文档开头声明字体目录。命令行场景往往推荐把字体文件放在操作系统字体目录中,这样编译进程直接按字体名查找即可。若不方便安装,也可通过 --font-path 参数指定目录:
bash复制typst compile --font-path ./assets/fonts main.typ output/main.pdf
3.3 导出多页图片与预览页面的常见做法
除了 PDF,Typst 还提供了输出 PNG 的能力。这种模式更适合生成带页码的预览图或幻灯片逐页图片:
bash复制typst compile --format png main.typ output/preview
输出目录中会为每一页生成独立图片,适合在网页里做分页预览。配合 Nginx 这类静态文件服务,就能快速搭建一个不需要浏览器端执行代码的文档预览页面。这种做法的好处在于,源文件内容在服务端已经被编译成不可执行的图片或 PDF,预览端不再需要解析 .typ 源文件,也就不会出现“预览源文件来自未授信的目录”的阻断问题。
4. 复杂项目中的 .typ 文件组织:多文件引用、包和模板的管理方式
4.1 源文件的模块化设计
当文档超过几十页或需要多个团队同时维护时,不建议把所有内容堆在单个 main.typ 里。Typst 提供了 #import 和 #include 两条路径组织代码:
#import用于引入另一个文件中的函数、变量、样式定义。#include则更多用于直接引入一篇完整文档内容。
日常项目里,公共函数与样式放在一个 lib.typ 或某个模板文件中,章节内容分别放在 chapters/ 的多个文件中,在 main.typ 里统一组装。例如:
typst复制// main.typ
#import "template.typ": *
#include "chapters/introduction.typ"
#include "chapters/background.typ"
#include "chapters/conclusion.typ"
这样做的好处,不只是文件更短了,更重要的是让源文件形成稳定的“入口、模块、资源”三层结构。当预览系统或 CI 流水线需要检查文件完整性时,只要入口文件存在,依赖模块和资源文件没有缺失,整个文档就能构建出来。
4.2 本地包与依赖管理
随着项目复用的模板越来越多,把样式文件复制到每个项目里并不是一个好方案。Typst 支持通过包管理系统来安装和管理本地库。一旦某个目录被注册为本地包目录,源文件可以像使用依赖库一样直接引用:
typst复制#import "@local/company-report:1.0.0": *
这种做法把格式和内容解耦:内容文件负责组织信息,格式文件负责排版规范。后续如果要统一更换公司模板风格,只需要更新包版本,而不需要逐个修改所有源文件中的 #set 命令和样式函数。
在团队协作时,我建议把模板包放在一个独立的 Git 仓库中,然后通过 CI 或脚本同步到本地包目录。这样版本变更记录在案,项目构建也可以复现。需要注意的是,不要在一个项目里混用多个模板包的类似定义,比如两个包都定义了 title-block,导入时就会产生命名冲突,需要用 #import "xxx.typ": title-block as report-title 这类别名方式解决。
4.3 模板文件中的陷阱:入口与子文件对路径的感知不同
多文件项目中,最隐蔽的路径问题不是图片找不到,而是“不同文件拥有不同基准目录”。如果你在 chapters/introduction.typ 中写:
typst复制#image("figures/architecture.png")
那么这个 figures 目录会被解析为 chapters/figures,和设计意图不符。正确的引用方式是让子文件里的路径相对于子文件自身,或者干脆统一把资源都放在根目录,并使用从入口文件出发的全局路径。
因为这一点,我会建议所有资源引用统一挂在一个 assets/ 根目录,并坚持在子模块中使用完整的相对路径。虽然日常使用中可以把子模块内容当成“整体的一部分”,但编译器解析文件时,每个文件都要先经过独立的词法解析,路径引用遵循的是它自己所在的物理位置。这一点和写 CSS 时在子目录样式文件里引用图片的情况很像,特别容易在项目迁移后出现资源大面积失效。
5. Typst 的沙盒约束与源文件权限问题:安全预警机制剖析
5.1 预览器里的“未授信目录”到底拦的是什么
回到最初那个“预览源文件来自未授信的目录,请停止访问!” 的提示。它不是 Typst 编译器自带的报错,而更常见于网页预览或第三方文件预览工具。核心要拦的,是“源文件被放到了执行上下文不可控的目录”。
在网页里,如果用户点击一个链接,预览工具在浏览器端或服务端动态载入文件,这个文件的来源可能是一个上传临时目录,也可能是另一个服务器的跨域路径。此时预览程序并不能完全确认源文件所在的目录结构、同级文件、引用资源是否安全。更何况 Typst 源文件支持模块导入和脚本调用,恶意构造的 .typ 文件可以在编译阶段触发大量文件读取请求,消耗服务器资源。所以很多在线工具会采用白名单策略:只有被用户主动标记为“信任目录”的路径,才能执行完整的预览逻辑。
这种设计其实不是一个产品的问题,而是任何把“用户提供的源文件”渲染成目标文件的技术方案都会遇到的安全边界问题。我在自建文档预览服务时也踩过类似的坑:一开始让后端直接拼接用户上传目录下的文件路径,结果浏览器返回了跨域阻断;后来决定先让用户把所有文件打包上传,服务端解压到孤立沙箱目录后,再对 main.typ 执行编译命令。整个过程中,服务端没有对任意磁盘路径开放读取权限,所有文件访问被限制在沙箱目录中。
5.2 为什么你在本地编译时很少遇到这个警告
本地命令行编译时,启动编译的进程通常继承当前用户的文件系统权限。如果项目文件就在本地,访问源码和资源属于正常操作。操作系统安全机制只是在必要时询问一次性授权,比如 macOS 的桌面文件夹访问、Windows 的受控文件夹访问。一旦授权,就相当于告诉系统:这个目录下的源文件是可信的。
因此,如果我是收到那条“停止访问”提示的用户,我会先判断自己的操作来源:
- 如果文件是从网盘或邮件附件里下载的,第一次用预览器打开,就不要轻易点击“信任”或“我已阅读并确认安全”。
- 如果文件是一份由同事通过企业网盘分享的完整项目包,并且里面包含多个依赖文件,那么正确的操作是先把整个项目包解压到本地一个有明确用途的目录,再进行信任或预览。
- 如果只是临时想查看
.typ里的文本内容,直接用文本编辑器打开即可,完全不需要触发预览器的编译沙盒。
在安全模型层面,真正安全的不是“点击信任”这件事,而是“源文件的执行环境是否可预期”。对于 Typst 项目而言,最稳妥的可信边界是:只信任你亲自创建或经过代码审查的文件所在目录,远离从临时目录、缓存目录、聊天工具下载目录直接打开预览的做法。
5.3 给自建预览场景的安全建议
如果你是需要给团队搭建 Typst 在线预览系统的开发者,下面的流程是我实际验证过的可用方案:
- 用户上传文件包后,服务端先解压到一个随机生成的隔离目录中。
- 使用
--root参数把编译根目录固定到这个隔离目录,防止文件路径穿越到磁盘的其他位置。 - 编译时限制最大执行时间,避免恶意文件导致无限循环。
- 编译结果转为 PDF 或 PNG 后,再提供给前端预览。
- 源文件包在预览完成后定时清除。
这样,无论 .typ 文件内引用了什么模块,都在受控的根目录下解析。前端拿到的只是最终图片或 PDF,不会把源文件夹的直接读取能力暴露给浏览器。这个方案不需要复杂内核隔离也能获得比较高的安全性,适合中小团队快速落地。
6. 我的长期维护实践与容易踩中的坑
6.1 每个 Typst 项目都应默认包含一个版本管理策略
.typ 文件是纯文本,天然适合用 Git 管理。版本管理能记录源文件和资源文件的每一次变化,这在文档生成体系里价值极大。你可以轻易比较两个版本的格式差异,也可以追溯某个页面样式是哪个提交引入的。
实践中我会在项目根目录维护如下结构:
text复制project/
├── main.typ
├── template.typ
├── chapters/
│ ├── introduction.typ
│ └── conclusion.typ
├── assets/
│ ├── images/
│ └── fonts/
├── output/
└── README.md
README.md 中记录编译命令、使用的 Typst 版本,以及模板包的安装方式。这样即使换一台电脑,新成员也能照着说明快速复现构建结果。
6.2 常见报错与排查思路
使用 Typst 源文件时,有一个很高的频率需要处理报错。列出几个对我影响最大的场景:
- 括号不配对:编译器报错可能出现在文件末尾而不是错误发生处,排查时优先检查新增段落里是否有
[、(、{未闭合。 - 函数参数少传或多传:Typst 函数参数名默认要求精确匹配,传入不存在的位置参数会直接报错。这时可以给函数定义补上完整参数列表,或者使用
..收集剩余参数。 - 图片路径错误:报错信息会指出无法找到文件。确认编译根目录和当前文件位置后,再修改路径。
- 字体缺失:编译不会报错,但页面会使用默认字体替换。检查命令行
--font-path或系统字体是否安装完整。
6.3 输出目录与源目录分离的好处
我见过很多用户喜欢让 main.typ 和 main.pdf 待在同一个文件夹,鼠标点一下就能找到结果。但在团队协作和持续集成场景下,源目录与输出目录分离更合理。可以配置编译命令直接输出到 output/,然后在 Git 中忽略这个目录。这样 Pull Request 里的变化全部是源文件的变化,代码评审可以聚焦在内容和样式定义上,不用频繁处理二进制 PDF 文件造成的冲突。
这里也顺带解释一个容易误解的事实:源文件里并不需要记录“输出 PDF 的路径”,输出路径是编译命令的一部分。你完全可以在不改动 .typ 内容的前提下,把它编译到任意目录。类型的完整性由源文件保证,输出路径只是构建参数。
6.4 这套总结落到实际操作后的体会
整理完 Typst 文件格式后,我最大的改变是开始用“解析器视角”而非“文本编辑器视角”来看待 .typ 文件。以前写多了会想当然地认为目录结构不重要,资源文件只要能打开就行;现在则会先在项目初始阶段就把入口文件、模板文件、资源目录、输出目录定义清楚。每一份文档源文件,背后都是一棵依赖树。理解这棵树,远远比记住某个具体排版命令更值得花时间。
再回到开头那条安全提示。处理这类报错,不一定要想尽办法绕过去,而应该退一步检查:这份文件从哪来?我要不要执行它?如果我不能确认它的整个目录结构安全可用,最基本的做法就是选择“停止访问”,改用文本编辑器检查内容,或者把整个项目包完整地放入可信文件夹后再决定是否执行预览。任何排版工具都替代不了这一层判断,毕竟那些后来让你追悔莫及的问题,往往就藏在最初那几秒钟的“我信任了不该信任的文件”里。
