Python is one of the most popular programming languages in the world, routinely THE most popular language. Its wealth of libraries makes it an ideal candidate for many domains, the clear front-runner for machine learning and data ventures, yet year after year it falls further behind the web development curve. In this article, I’ll try to convince you that the reason for this is tooling, and that the reason it took so long for a solution to emerge (Python Fragments) is due to the utter state of the JavaScript ecosystem.
What is fragments?
Fragments is a DSL transpiler that extends Python to include modern HTML template rendering in much the same way as React and Vue:
def IndexPage():
return <>
<h1>Hello, World!</h1>
<div for={{ post in POSTS }}>
<h2>{{ post.title }}</h2>
<a href={{ f"/post/{post.slug}" }}>Read More</a>
</div>
</>
Sick of relying on Jinja2, I made Fragments as a direct mechanism to improve development velocity while working on my company, The Running Algorithm.
What’s Jinja? (and what’s wrong with it)
Jinja is the most popular template rendering tool in the Python ecosystem currently. It’s used natively in Django, Flask, and FastAPI - the big three that utterly dominate the Python web development space.
I don’t mean to talk badly about it - Jinja has been around for a long time and has done it’s fair share of work, but in the face of modern web development solutions it just doesn’t hold up anymore.
{% macro page(posts) %}
<h1>Hello, World!</h1>
{% for post in posts %}
<div>
<h2>{{ post.title }}</h2>
<a href="/post/{{ post.slug }}">Read More</a>
</div>
{% endfor %}
{% endmacro %}
The syntax (above) isn’t HTML native, it’s a general purpose document templating engine. Templates live in specific template files, with their own language and control flow - template files are evaluated at run time through Jinja library calls to template files. The result is the lack of the features that are pushing JavaScript’s popularity ahead in the web dev space:
- No intellisense with variables or function calls,
postscould contain absolutely anything - Template files miles away from the places they’re being called and the logic they’re being rendered with
- Non-HTML-native syntax ends up being extremely verbose
So, what’s right with Fragments?
Fragments sidesteps these problems:
- Bringing the HTML into the endpoints and functions populating that HTML closes the gap and makes for much more intuitive software
- Fragments as a superset of Python means Python intellisense can naturally be used when writing fragments, reducing cognitive complexity and time to implement for new templates
- The HTML native syntax keeps templates minimal and developer friendly, further reducing cognitive overhead
Additionally, the cost to using Fragments is extremely low: just install it with pip install python-fragments and then include the line from fragments import loader prior to importing a file containing fragments - that’s it. There’s no build step or additional tooling, it’s ready to go after typing a grand total of seven words.
We achieved this using Python’s import hook system, allowing us to transpile imported Python modules in flight at the time that they’re imported. Average transpilation time is 120μs for medium sized modules, so even if you had thousands of templates you’re still adding only a fraction of a second to your web server start time.
Despite all its benefits, the core functionality (not including LSP) of Fragments still only comes to 500 lines of code - including whitespace and comments - and has no external dependencies.
Why are we the first to do it?
As promised, I’ll now try to convince you that the reason nobody has tackled this before is because of JavaScript.
The JavaScript ecosystem is many things, but “suffering for lack of choice” is not one of them. Off the top of my head, you could use any one of:
- React
- Vue
- Angular
- Solid
- Svelte
To achieve the “modern standard” I set out earlier. Not to mention the variety in build systems:
- Vite
- Webpack
- Rollup
- Parcel
- esbuild
- Turbopack
Each of these systems absolutely DWARF Fragments in size, with sources routinely ranging into the hundreds of megabytes after all dependencies are counted. The combined source of the natural grouping: Fragments + AlpineJS + HTMX is less than 1 MB. The story is similarly simple for Go’s Templ.
The established status quo is that web developer tooling is extremely difficult to build and maintain, just ask Claude (Sonnet 4.6):
How big a technical undertaking would it be to add React-like syntax to Python? How many lines of code roughly? How long to implement? I am referring to true JSX-like implementation, with full LSP support. 1 sentence estimation
Enormous — you’d need a custom preprocessor/transpiler (CPython grammar fork or import hook), IDE tooling plugins for every major LSP, and type-checker integration (mypy/pyright), realistically 10,000–50,000+ lines across all components and 1–3 years for a solid, production-ready implementation by a small team.
Suffice to say, this wasn’t quite accurate, based entirely on similar JavaScript efforts.
The question remains: why has JavaScript overcomplicated this problem so extremely?
Frankly, I have no idea. Some of it comes down to JavaScript’s language design - import hooks are not so possible, build-time transpilation is the norm. Mostly, though, I would put it down to one design decision: rendering all of your HTML based on data in the front end requires extensive and unnecessary overhead.
Years later, NextJS has server side components. Do those components render HTML and send it to the browser? Nope. They render a load of JSON, send that to the browser, then do a load of secret trickery to turn that JSON into your HTML components, all the while pretending that none of this is happening. The hoops to jump through to transmit HTML from server to browser are extraordinary.
When should you make a new tool & what makes it useful?
- It’s never right to make a new tool, until it is
- Some problems really are as simple as they seem
- Use the existing tools until you’re actually familiar with the problem space and have concrete critique
- Even after you’re familiar with the problem space, you’re not necessarily familiar with the solution space
- Often 5 minutes of thought for 12 days is better than 60 minutes of thought on one day - let it stew
- After that, it’ll be obvious if you need to build a new tool
- Or just build it for fun anyway, because building tools is a great learning experience