Our free shadcn/ui kit for Figma has been updated to 2.0. Get the Community Edition →

Jetbrains language plugins: Nunjucks & Liquid

Written by Johan Ronsse

A small demo of the Nunjucks plugin - click the different tabs
{#
  layouts/base.njk — the page every other template extends.
  Multi-line comments fold too, down to a single placeholder.
#}
<!DOCTYPE html>
<html lang="{{ site.lang | default("en") }}">
  <head>
    <meta charset="utf-8">
    <title>{% block title %}{{ page.title }} · {{ site.name }}{% endblock %}</title>
  </head>
  <body class="page page--{{ page.kind | default("plain") }}">
    {% include "partials/header.njk" ignore missing %}

    <main id="content">
      {% block content %}{% endblock %}
    </main>

    {% block scripts %}
      <script src="/js/app.js" defer></script>
    {% endblock %}
  </body>
</html>
{% extends "layouts/base.njk" %}

{% block content %}
  <ul class="posts">
  {%- for post in posts | sort(attribute="date", reverse=true) %}
    <li class="post {{ "is-featured" if post.featured }}">
      <a href="{{ post.url }}">{{ post.title | title }}</a>
      <time datetime="{{ post.date }}">{{ post.date | date("D MMM YYYY") }}</time>

      {% if loop.index <= 3 and post.excerpt %}
        <p>{{ post.excerpt | truncate(140) }}</p>
      {% elif loop.last %}
        <p class="muted">…and {{ posts.length - 3 }} older posts.</p>
      {% endif %}
    </li>
  {%- else %}
    <li class="empty">Nothing published yet.</li>
  {%- endfor %}
  </ul>
{% endblock %}
{% from "macros/forms.njk" import field with context %}
{% import "macros/icons.njk" as icons %}

{% macro field(name, label, type="text", required=false) %}
  <p class="field">
    <label for="{{ name }}">
      {{ label }}{% if required %}<abbr title="required">*</abbr>{% endif %}
    </label>
    <input id="{{ name }}" name="{{ name }}" type="{{ type }}"
           {{ "required" if required }}>
  </p>
{% endmacro %}

{% call panel("Sign up") %}
  {{ field("email", "Email address", type="email", required=true) }}
  {{ field("password", "Password", type="password", required=true) }}
  <button type="submit">Continue {{ icons.arrow("right") }}</button>
{% endcall %}
{# Filters chain left to right; tests read like English. #}
{% set featured = posts | selectattr("featured") | list %}

{{ user.name | trim | title | default("Anonymous", true) }}
{{ cart.lines | map(attribute="total") | sum | round(2) }}
{{ tags | join(", ") | upper }}

{% if user.email is defined and user.email is not none %}
  <a href="mailto:{{ user.email | urlencode }}">Email {{ user.name }}</a>
{% endif %}

{% for n in range(1, 4) %}
  {{ n }}{{ ", " if not loop.last }}
{%- endfor %}

{% filter escape %}
  <em>Escaped by the surrounding block filter.</em>
{% endfilter %}

In June, I saw a market opportunity to build a plugin for Jetbrains editors like Webstorm and IntelliJ for the Nunjucks templating language.

I created a first version of a Nunjucks syntax plugin and posted it on Jetbrains Marketplace. I mostly made it for my own needs but thought it would be good to post it online. A few weeks later I saw it was downloaded over 200 times, and that made me think.

After some initial market research, I concluded that a) the official Nunjucks plugins was severely outdated and b) The main competition was charging $5+/month for a syntax plugin, and I didn't love that subscription type approach, especially for the subject matter of a syntax highlighting plugin.

This led me to taking a stab at improving the plugin. I got to a good result, started using it myself, and then made it commercially available for a one-time $12 fee, and created the marketing site.

In July, I followed up with a plugin for Liquid’s templating language. Liquid’s plugin is particularly interesting, because it connects to the Shopify Liquid language server. A language server can check your project against common errors. It can scan a project’s context across files and is way more powerful than an individual file check (e.g. for fixing component import issues)

Later in August, I also started working on a Svelte plugin, which hasn’t been released yet due to time constraints. I use it for my own work and it’s pretty cool. There’s a Svelte plugin on the Jetbrains community that’s not so hot with pretty bad reviews. It also connects to a language server using LSP (Language Server Protocol).

Content-wise, this is actually pretty fun to work on: syntax highlighting is based on a lexer, that has to understand the language and it’s constraints. Since Nunjucks and Liquid are “mixed” languages this can get quite challenging to get right. By mixed languages, we mean they can contain more than just 1 language - files might contain inline CSS blocks or JSON,

It doesn’t stop at syntax highlighting: Jetbrains editors have lots of small features for productivity like code block collapsing, live editing and automatic reformatting. In order to create a good plugin, one must also understand those features in order to support them well.

This is a small side project that’s currently responsible for just $275 dollars in revenue across a few months; but it’s one of those things that might just stay running for years with minimal upkeep. I’m looking forward to see how it evolves.

Johan Ronsse

As the founder of Obra Studio, Johan's mission is to help software companies get to the next design level. He’s forever looking for the perfect balance between aesthetics and usability.