A package manager has been one of the top feature requests for Typst ever since our open source launch. About one week ago, we decided that it is time to deliver on that request. This post explains how we designed and built a minimum viable package manager for Typst in one week.

Package managers like npm and cargo have tons of features and building a full equivalent to that for Typst would have been a huge time investment. Do we need all their features though? For Typst, we just want a place where the community can share useful building blocks and automations without copying around files or folders into each of their projects. In this spirit, we took a step back and sketched out a design for a minimum viable package manager that a single person could build in a week. The result of this work ships in our web app and command-line compiler today.

For our minimal package manager, we had the following design goals:

  • Works in single-file scenarios
  • Compilations are reproducible
  • Supports global community packages and local system packages
  • Downloads packages on-demand
  • Packages work offline once downloaded

The package repository

For the package submission process, we took a big shortcut: Submitting a package means making a pull request to our package repository with the full contents of the package. A package has a name, a version and lives in a namespace. It is imported as #import "@{namespace}/{name}:{version}".

For now, all packages submitted to the shared repository live in the preview namespace. This way, we can deal with the allocation of namespaces to users and organizations later. We would not want random people claiming namespaces that should belong to universities or other institutions, but do not have infrastructure to perform validation now. The preview namespace also leaves open the option of having a non-namespaced global registry in a future iteration of the package manager.

Within the package repository, all packages are stored in folders named packages/preview/{name}-{version}. A package that has multiple version is stored there multiple times. The package repository has a GitHub action that builds a tar.gz archive for each package and an index.json with metadata about the available packages on each push. It then uploads the packages and index to https://packages.typst.org/preview, which is served through a CDN. The index is served with Cache-Control: must-revalidate because it changes frequently whereas packages are served with a max-age of 90 days because they don't change.

Package format

A package must contain a typst.toml manifest file that is heavily inspired by Cargo.toml. The three keys name, version, and entrypoint are mandated by the compiler. There are a few more metadata keys, some of which are required for submission to the shared repository and some of which are completely optional.

[package]
name = "example"
version = "0.1.0"
entrypoint = "lib.typ"

Package authors are free to structure their packages however they want. The only requirement is that the entrypoint key must point to some .typ file. Within a package, everything is encapsulated: Package code can only read files in the package and absolute paths are resolved relative to the package root.

How packages are accessed

When the command line compiler encounters a package import, it searches for the directory typst/packages/{namespace}/{name}-{version} within the OS-dependant data and cache directories.

The location in the data directory is intended for storing system-local packages while the location in the cache directory is populated through on-demand downloads of packages from the shared repository. As packages are only downloaded on use instead of upfront, Typst remains quick and easy to install.

Reproducibility is ensured through two factors:

  1. We do not allow removal or change of packages after submission (an exception would require a really good reason).
  2. Every import must specify the full package version. While this is a bit inconvenient, it means that we don't need a manifest or lock file for reproducibility. It is a design goal that the typst compiler works well for documents consisting of just a single .typ file and that it does not create any auxiliary files during compilation.

Crucially, because package contents do not change, a package only ever needs to be downloaded once. The CLI does not ever download the index.json and importing an already downloaded package does not result in any network access. This makes for a great offline experience.

Finding packages

A list of all community packages is available directly in the packages section of our documentation. Like the rest of the package management, this is also kept as simple as possible. It's a plain HTML page that fetches the package index client-site and displays it in a searchable table.

Each table entry links to the package's documentation in the form of a README file in package repository. It also offers a small button for copying the relevant import statement (great suggestion from our Discord). Down the road, we plan to build a documentation generator like rustdoc for Typst, but for now the documentation setup is maximally simple.

In addition to the documentation page, the package list is also available through the autocomplete panel in the web app. There are already a few packages available: The tablex package fills the current feature gaps of Typst's built-in tables while quill draws beatiful quantum circuits.

Summary

That's it! Won't get much simpler than serving a bunch of tar.gz files through a CDN and downloading them on-demand. To be clear: I'm not saying that this is the ultimate package manager. But, for Typst, it solves the immediate problem of people having to copy-paste packages into their projects in a very simple and, at least to me, elegant way.

With the package manager itself shipped, there's still one more thing we want to build around it in the near future: Designated support for template packages that show up in the web app's template gallery and support scaffolding with a CLI command.

Thanks for reading. I'd be happy to hear your thoughts! Have a good weekend and maybe submit a package?