Manage the organisation and compilation of multiple Typst documents.
Declare the tree of documents you want to compile — a schema of sections and resources — and typst-press compiles every resource to a PDF in one pass. Compilation runs in-process via the native @myriaddreamin/typst-ts-node-compiler, so no Typst CLI is required.
npm install typst-pressRequires Node.js 18+. The package is ESM-only.
The simplest usage needs no subclassing and no type parameters:
import { Press } from 'typst-press';
const press = new Press('./src', './build', [
{
id: 'mathematics',
name: 'Mathematics',
items: [{ id: 'notes', name: 'Notes' }],
},
{
id: 'physics',
name: 'Physics',
items: [
{ id: 'notes', name: 'Notes' },
{ id: 'formula-sheet', name: 'Formula sheet' },
],
},
]);
await Promise.all(press.run());- The schema is a tree. Nodes with
itemsare sections; leaf nodes are resources, i.e. the documents to compile. - Every node has an
idand aname.iddetermines the file paths;nameis just a human-readable label. new Press(sourceDirectory, outputDirectory, schema, defaultOptions?, root?)— the last two arguments are optional.run()returns an array of promises, one per resource. Callers mustawait Promise.all(...).
This example compiles:
| Source | Output |
|---|---|
src/mathematics/notes.typ |
build/mathematics/notes.pdf |
src/physics/notes.typ |
build/physics/notes.pdf |
src/physics/formula-sheet.typ |
build/physics/formula-sheet.pdf |
By default, each resource path is the branch of node ids joined together, prefixed with the source or output directory. The .typ / .pdf extension is appended automatically when missing.
Each node can override its part of the path with sourcePath / outputPath:
false— omit this level from the path;null— (output only) reuse the corresponding source path;- a string — use it instead of
id.
Options configure individual resources. They come from two places, merged with the resource's own options taking precedence:
defaultOptions(4th constructor argument) — applied to every resource;optionson each resource node.
ResourceOptions supports:
sysInputs— values passed to Typst'ssys.inputsfor this resource.mainFile— compile a directory entrypoint instead of a.typfile named after the resource.truelooks formain.typ; a string names a different entrypoint:
{
id: 'notes',
name: 'Notes',
options: { mainFile: true }, // src/.../notes/main.typ
}Beyond options there are two further, independent ways to customize a press: extra fields and subclassing.
Add typed metadata to sections, resources, options or nodes by providing a PressCtx type parameter — no subclassing needed. PressCtx<TSectionExtra, TResourceExtra, TOptions, TNodeExtra> describes the extra fields allowed at each level:
import { Press } from 'typst-press';
import type { PressCtx } from 'typst-press';
enum ResourceType { NOTES, CHEAT_SHEET }
type Ctx = PressCtx<
{ shortName: string }, // extra fields on sections
{ type: ResourceType; version: string } // extra fields on resources
>;
const press = new Press<Ctx>('./src', './build', [
{
id: 'subject',
name: 'Subject',
shortName: 'SUB',
items: [
{
id: 'notes',
name: 'Notes',
type: ResourceType.NOTES,
version: '1.0.0',
},
],
},
]);
await Promise.all(press.run());Extra fields only add compile-time type checking; they do not change behavior by themselves.
Subclass Press<Ctx> to change how documents are compiled. The most useful methods to override:
getSystemInputs(nodePath, resource)— project-wide Typstsys.inputs, computed per resource. Returned inputs are merged over the resource's ownsysInputs.getSourcePath(nodePath)/getOutputPath(nodePath)— compute the input/output file paths (default: the branch of nodeids plus the extension).compile(inputPath, outputPath, nodePath, resource)— full control over the compilation step (default: write the PDF via the native compiler).
When you override a method, you don't need to worry about the exact Ctx['TNode'] / Ctx['TResource'] types — just copy the signatures from the base class. They exist so that inside the override you can read the extra fields you declared in Ctx: for example, the code below accesses resource.version, which is only available because version was declared in the PressCtx type. Without those types you'd lose your metadata (and type checking) in the override.
class MyPress extends Press<Ctx> {
protected override getSystemInputs(
nodePath: Ctx['TNode'][],
resource: Ctx['TResource'],
): Record<string, string | object> {
return { ver: resource.version };
}
}
const press = new MyPress('./src', './build', schema, { mainFile: true });
await Promise.all(press.run());root (5th constructor argument) sets the compiler workspace, e.g. './' so resources can #include files outside the source directory.