The Worst Way to Add Clojurescript to your web page

ClojureScript is, in my estimation, the best compile-to-JS language available. I have tried many, and while each has some enjoyable qualities, none satisfy all my (admittedly limited) needs/wants as a coder. I have long thought that what JavaScript is missing is not a type system, but a complete language implementation. ClojureScript provides that, and tooling like Shadow-CLJS gives you the same robust access to the wealth of JS libraries that JS programmers lean on to do most of their consequential work.

I face a conundrum, though. I don't like web development, and I don't like the ceremony of single-page apps. They are cool, and CLJS's solutions for React-based SPAs are still state-of-the-art many years after their initial release. Recent efforts have been made to keep CLJS modern, while not giving up on the things that made it much more advanced than JS at the time of its inception. Projects like Squint have been the result, and last year's release of ClojureScript added support for :lite-mode.

Unfortunately, I have found it difficult to achieve a balance between the benefits of CLJS and some of the contingencies one is forced to reckon with, like the historical deep coupling between React and CLJS, a situation only very little bettered by some of the innovations mentioned just above.

Recently, I started learning Perl. I surprised myself by enjoying it. It's got clunky syntax and sometimes it doesn't do what I mean. Still, for a language that's turning 40 next year, it has some nice features, and it satisfies my mental model of programming, which is approximately the same as the one I used when I was 12 and I passed the time by obsessively solving Sudoku puzzles.

These days, I'm just a hobbyist, having "retired" from professional coding in February 2025. Maybe I will return some day, but if that proves to be the case, I do not doubt that my contributions will be the richer for having amused myself by finding the most offensively elaborate ways to do small things.

Enter The Worst Way ⚰️ to add ClojureScript to your web app.

One of Perl's particularly absurd (read: useful) features is the __DATA__ directive. You can tack it onto the end of a Perl file and the Perl interpreter, when once it encounters this symbol, will stop parsing Perl code. You can put just about anything here, and it will be available in the program above through a file handle called...wait for it: DATA.

This file handle is stateful and can be seeked on and what not.

Perl has a cool, modern web framework called Mojolicious. It has several intriguing features, not the least of which is a custom data pipeline syntax that is described here. Another of its value propositions is true single-file web apps, for the simple cases. Using __DATA__, Mojolicious allows appending HTML templates to the same file that defines your app's routing table and any other business logic.

I know "separation of concerns" is the idée fixe of the industry (except when it isn't cough cough CSS-in-JS cough cough), but when you have just a few routes and a small number of templates, having the ability to reference things without jumping around from file to file (even with an IDE) is a breath of fresh air.

Howsoever that may be, what follows is such an egregious violation of all good sense that I will not pretend to hide behind such flimsy excuses as making counterarguments against the customs of the web development world.

Behold, maybe the worst code you will see all day:

#! /usr/bin/perl

use Mojolicious::Lite -signatures;

open my $cljs, '>', 'src/hello_world/core.cljs' or die;
while (<DATA>) {
    my @source = grep { m/(ns hello-world.core/ ... m/;{5}/ } <DATA>;
    for (@source) {
        print $cljs $_;
    }
}

get '/' => sub ($c) {
    my $script = $c->asset_tag("/main.js");
    $c->stash(cljs => $script);
    $c->render(template => "index");
};

app->start;

__DATA__
;;;;;
(ns hello-world.core)

(def p-el (.getElementById js/document "greeting"))

(set! (.-innerText p-el) "Hello ClojureScript, from Perl 5!")
;;;;;
@@ layouts/top.html.ep
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
</head>
<body>
<%= content %>
<%= $cljs %>
</body>
</html>

@@ index.html.ep
% layout 'top';
<div>
<p id="greeting"></p>
</div>

This keeps the $cljs filehandle open, and thanks to the code reloading done by Mojolicious, DATA is automatically updated, too, meaning that while loop runs on every save. The CLJS hot reloading done by Shadow just works, even if this example is cursed and possibly useless to others.

Insofar as this post is more than a joke, it might serve to offer a brief closing justification for its existence. My logic is as follows: I am just one guy of whom it is maybe going too far to say that I am building "real" apps. Indeed, it may also be false to say that I am having fun, in any ordinary sense of that word. That being said, I was quite tickled when I saw that I succeeded in making this proof of concept. I was worried that there would be race conditions and unexpected behavior with the DATA filehandle, but everything just runs as I expect it to. DWIM has a new and extremely diabolical friend.