DEV Community

Mohammad Raziei
Mohammad Raziei

Posted on

I Needed to Run Mermaid.js in Python. So I Built Two Libraries.

It started with a single line in a requirements doc:

"Diagrams should be auto-generated as part of the build pipeline."

Simple enough, right? I was building a documentation automation tool in Python. The diagrams were written in Mermaid — clean, text-based, version-controlled. All I needed was to convert .mmd files to SVG during the build.

I looked up the standard way to do it.

npm install -g @mermaid-js/mermaid-cli
Enter fullscreen mode Exit fullscreen mode

I stared at that line for a moment. This was a Python project. A pure Python project. And now I needed Node.js, npm, and — I kept reading — Chromium running headlessly in the background, just to turn a text file into an SVG.

I closed the tab.


The Search for Alternatives

Surely someone had solved this already. I started digging through PyPI.

The Python packages that claimed to render Mermaid either called out to the npm tool under the hood (so you still needed Node.js), hit a third-party API (so you needed internet access and an API key), or just... generated the Mermaid syntax and left the rendering to you.

None of them actually rendered diagrams. Locally. In Python. Without external dependencies.

I went deeper. What does Mermaid.js actually need to render? I read through the source. It needs a real DOM — document.createElement, CSS computed styles, SVG measurement APIs. It's not just parsing text; it's doing real browser-level layout to figure out where nodes go.

That's why everyone reaches for a browser. Mermaid genuinely needs one.


The Realization

At some point I remembered PhantomJS.

Most people think of PhantomJS as a dead project — and for web scraping and UI testing, it is. Playwright killed it for those use cases. But PhantomJS is, at its core, a self-contained WebKit binary with a full DOM implementation. It hasn't had a new release since 2018, but it also hasn't needed one for what I needed it for. It's frozen in time, which for a reproducible build environment is actually a feature.

The question was: could I build a clean Python interface around it that would let me inject Mermaid.js into PhantomJS and capture the SVG output?

I started building phasma.


Building Phasma

The first design goal was zero setup. If you had to install PhantomJS separately, I hadn't actually solved the original problem. So phasma bundles the PhantomJS binary directly — it ships with the package, across Windows, Linux, and macOS.

pip install phasma
# That's it. PhantomJS is included.
Enter fullscreen mode Exit fullscreen mode

The core of phasma is driver.exec — a way to run JavaScript files directly through the bundled PhantomJS binary, with full DOM and WebKit support:

from phasma.driver import exec as phantomjs_exec

# Run any JS file with full PhantomJS/WebKit environment
result = phantomjs_exec("my_script.js", capture_output=True)
Enter fullscreen mode Exit fullscreen mode

This was actually all that mmdc needed. I just needed to run a JS file that loaded Mermaid, rendered a diagram, and printed the SVG. The driver.exec interface handled it cleanly.

But while I was at it, I kept going.


The Playwright-like API

Once the core worked, the API felt obvious: make it look like Playwright. If you've used modern browser automation in Python, Playwright's API is the gold standard. Clean async, familiar method names, intuitive page/browser hierarchy.

import asyncio
from phasma import launch

async def main():
    browser = await launch()
    page = await browser.new_page()

    await page.goto("https://example.com")
    title = await page.text_content("h1")
    await page.screenshot(path="capture.png")
    await page.pdf(path="page.pdf")

    await browser.close()

asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

The async implementation still has rough edges — this is an area where contributions are genuinely welcome. If you're comfortable with Python async internals and want to help bring the Playwright-like API to full stability, the repo is open and PRs are very much appreciated.


Then Building mmdc

With phasma working, building mmdc took surprisingly little code. The hard problem was already solved.

from mmdc import MermaidConverter
from pathlib import Path

converter = MermaidConverter()
converter.to_svg("graph TD\n  A --> B --> C", output_file=Path("diagram.svg"))
converter.to_png("graph TD\n  A --> B --> C", output_file=Path("diagram.png"))
converter.to_pdf("graph TD\n  A --> B --> C", output_file=Path("diagram.pdf"))
Enter fullscreen mode Exit fullscreen mode

No Node.js. No npm. No Chromium. Just:

pip install mmdc
Enter fullscreen mode Exit fullscreen mode

The CLI mirrors the official mermaid-cli syntax, so if you're already familiar with it, switching is trivial:

mmdc --input diagram.mmd --output diagram.svg
mmdc --input diagram.mmd --output diagram.png --timeout 60
Enter fullscreen mode Exit fullscreen mode

What I Actually Shipped

Two packages, one problem:

phasma — a Python interface for PhantomJS with a bundled binary, driver.exec for direct JS execution, and a Playwright-inspired async API (in active development). For anyone who needs to run JavaScript with real DOM support inside Python.

mmdc — a Mermaid diagram converter built on top of phasma. Converts .mmd files to SVG, PNG, and PDF. Fully offline, no system dependencies beyond pip install mmdc.

Both are on PyPI. Both are MIT licensed. And both genuinely do what they claim.


The Honest Part

PhantomJS is old. It doesn't support ES2020+. Its async story required careful handling. And the Playwright-like API in phasma is still maturing — the sync paths are solid, but the full async implementation needs more work and testing.

But for the original problem — render Mermaid diagrams from Python with zero external dependencies — it works. Reliably. On every platform.

If you're building documentation tooling, CI pipelines, or any Python project that needs diagrams without Node.js, give mmdc a try. And if you're interested in the lower-level plumbing — running arbitrary JavaScript with a real DOM inside Python — phasma is the piece you want.


⭐ If either of these saves you from writing npm install in a Python project, a star on GitHub goes a long way:

And if you want to help stabilize the async Playwright-like API in phasma — PRs are open and very welcome.

Top comments (0)