Back

Releasing `@nchagnet/remark-uv`

It’s King’s Day here in the Netherlands. This means DJs in the streets, lots of beer, and most importantly a long weekend. I’ve been using that time to work a little bit on my blog, and I finally took the plunge on a long-time item on my wishlist.

When writing these articles, I often want to run a small piece of code, or generate a simple plot. But keeping extra script files and having to remember to run them at build time can be a real pain. I much prefer a declarative approach where the code is right there in the article, and the output is automatically generated and inserted at build time. Essentially, I want something like a Jupyter notebook while remaining within the Astro ecosystem.

Technically speaking, this is not very difficult. There are a lot of plugins out there (for either Markdown or MDX) which do the same thing for diagrams or math. Take for example the rehype-mermaid plugin, thanks to it, you can just use a code block with the mermaid language and it will directly generate the appropriate diagram and insert it within the HTML tree.

However, when it comes to Python, things are a little bumpier. You have to handle the Python runtime, environment with dependencies, output, etc. Thankfully, the uv package manager renders all these obstacles moot. If you don’t know it, uv is rapidly becoming a new standard1 in Python development. Within a single binary, uv handles all Python versions (replacing pyenv), virtual environments (replacing poetry) and package builds. And it does all that incredibly fast. Paired with its aggressive caching and its ephemeral environment system, uv is the perfect choice to run one-off Python code snippets within milliseconds.

The other great pillar of this project is PEP 723 which introduced inline metadata for scripts. So no need for a separate requirements.txt or pyproject.toml, with this PEP, a script can specify its own dependencies using a comment block at the top of the file. For example, to add matplotlib as a dependency, you can just add the following to the top of your script:

# /// script
# dependencies = [
# "matplotlib",
# ]
# ///

From these, I built @nchagnet/remark-uv (which you can also find on the npm registry), a remark plugin which places itself in the Markdown processing pipeline, looks for code blocks with the pyRun language (a customizable choice), runs the code snippet using uv,2 and uses the stdout output as a source for a new HTML node replacing the original code block.

When making this plugin, I wanted to focus on using it to generate plots, so when specifying the metadata of the language, I added support for img or svg (on top of just having text) so that the output can be directly embedded within an <img /> or an inline <svg>.

Examples

Here’s an example of how it works in practice:

```pyRun
print("Hello, World!")
```

which will be transformed into the following code block

Hello, World!

For an image, the plugin provides within the Python runtime the matplotlib and plotly dependencies, as well as functions to convert figures to base64 (for images) or to SVG HTML content. For example,

```pyRun svg [numpy]
import numpy as np
from plotly import graph_objects as go
x = np.linspace(0, 10, 100)
y = np.sin(x)
fig = go.Figure(data=go.Scatter(x=x, y=y, mode='lines'))
fig.update_layout(
title="A simple plot",
xaxis_title="x",
yaxis_title="sin(x)",
margin=dict(l=20, r=20, t=50, b=20),
)
print(plotly_to_svg(fig))
```

renders the following SVG image at build time:

0246810−1−0.500.51A simple plotxsin(x)

To show some features, I added an extra dependency (numpy) by using the inline metadata syntax described above. The plugin will automatically install numpy in the ephemeral environment before running the code snippet.

There it is, I hope this plugin can be useful to you as well. In the README of the project, you can find more details about installation within an Astro project, as well as all configuration options available. I don’t plan on updating this plugin more than I need to (I made this mostly for my personal use), but if you have any issues or feature requests, feel free to open an issue on the GitHub repo and I’ll try to get to it when I can.

Footnotes

  1. I’m quite aware of its reputation as a VC-backed dev tool, and now with the recent news of its acquisition by OpenAI, these fears are only amplified. But I think the important part is that Pandora’s box has been opened. Even if tomorrow I must switch to a different tool, I would expect that tool to at least reach feature-parity with uv.

  2. The plugin also supports caching of the output, so that the code is only re-run when the snippet or its dependencies change.