On the Updated Design of My Blog

Published 12 April 2020 · Updated 12 April 2020

In this post I’m quickly describing my efforts to theme a plain vanilla Hakyll front end.

Motivation

Hakyll comes with a very basic plain black-and-white theme in paper style. Thanks to Pandoc, code blocks have all the markup, but it remains ignored initially. In what comes next I will describe how I’ve realized the present design. I need to take some screenshots for comparison…

Vanilla Hakyll

Hakyll is a static website generator. In fact, you can consider it a framework, that is, it is a library that provides you with an API to the static website generator. You then define that generator and compile it, and when the binary is executed, you obtain your website.

The basic Hakyll does not provide anything beyond that framework. It is nice and minimalistic, the API quite ergonomic.

But modern website design embraces JavaScript. On the client side, there is a balance to strike between interactivity and data transfer. Too much data transfer can eat even the most generous data plan on mobile devices. From the developer perspective, the ease of development, including QA of course, and maintenance are crucial. Hakyll excels at both, albeit not very versatile. This is its specific purpose.

It is quite easy to supplement JavaScript into the templates.

Issues Arising

Mobile-Friendly

Accessibility-Friendly (Screenreaders etc.)

State of the JavaScript Ecosystem

Modern Front End

Choice of the Build Tools and the Lifecycle Management

Build automation is a boon. On this tiny scale of a blog, at first setting up a proper dev environment with a good pipeline is an effort not worth the hassle. But as the complexity will rise, I will adopt more lifecycle management and automation tools, and blog about them.

For now, I’ve come to use Gulp + Babel + TypeScript + Parcel.

Next I’m planning to create a CV route, improve the interaction, adopt Stencil to create good web components, so in the end this blog looks and feels like a mobile app, and make it a proper PWA.

Choice of a Front-end Compiler: GHCJS vs PureScript vs Elm vs ReasonML vs TypeScript vs JavaScript

A thorough comparison deserves a separate post. Stay tuned. But for now, since I don’t yet need a full-fledged front end, there’s no reason to embrace something that would go far beyond plain vanilla JavaScript. As of writing, I settled with TypeScript, which is closest in syntax and spirit perhaps to C# starting with the 7-series versions. I’m enjoying using the modern C# a lot, so I will dedicate a separate series of blog posts, especially pertaining to its memory model, evaluation strategies, and most certainly the amazing .NET framework that lends its power to it.

If you feel unfamiliar with these notions, a front-end framework is a collection of libraries and perhaps tools that facilitate the development of dynamic interfaces, whose logic of user interaction is driven usually by JavaScript, there is also WebAssembly (Wasm), though it is less mature than the established JavaScript engines.

There is a separation between what is called “back-end” and what is called “front-end,” where the former refers to the server code and the latter to the code executed on the client machine. Modern versatile web browsers such as Chrome (and Chromium), Safari, and Firefox (Microsoft Edge uses Chrome or Chromium under the hood) embed JavaScript engines, which interpret and provide a runtime execution environment. There is also a very popular backend engine known as Node.js.

In other words, with JavaScript we can drive both server-side processing logic and client-side user interface code.

Now JavaScript is a very powerful language but poses huge risks without costly and error-prone due diligence. Using static code analyzers such as ESLint, a host of error classes can be rooted out. Still, the stronger the type system, the fewer typing errors the code will contain.

Cue in TypeScript. There have been several approaches to introducing types to JavaScript. Arguably the most successful has been TypeScript, with a very advanced ecosystem and developer and user community. A lot of modern software is described in TypeScript. As I noted above, C# comes closest in syntax and spirit.

Among others, there are also PureScript, Elm, GHCJS and framework-specific EDSLs (embedded domain-specific languages).

PureScript reflects the Haskell ecosystem. Elm loosely reflects Haskell. GHCJS is an attempt at transforming Haskell code directly into JavaScript.

In addition, certain future features of ECMA Script can be backported in a sense with tools such as Babel. And a step further back, Polyfills provide transformations from modern JavaScript to older language, which is supported by the older browsers.

Personally I find PureScript simply amazing. Its developer community even managed to improve upon Haskell in certain respects.

What’s common to all of them is that they all compile down to vanilla JavaScript, with polyfills where necessary. This is similar to the hierarchy of abstraction in systems languages, where we’ve seen over the decades that

machine code <- bytecode <- asm <- BASIC/C/Fortran/C++93-98 <- ...

where the ellipsis refers to all our modern languages whose runtime machines are compiled from any of those languages. Rust has descended down this hierarchy. And in the JavaScript world, JavaScript is the target language. On a side note, there’s a somewhat weird competition in Quines going on, where language compilation goes full circle.

As always and everywhere, there are pros and cons. Most obviously, almost all changes to the deep parts of JavaScript will most likely be handled by the developers of those platforms. It is fairly rare that a change in ECMA Script will result in a change in the API that you’ve been using in a higher-level language such as TypeScript or PureScript. So you enjoy API stability with improvements under the hood. For example, TypeScript developers refused to introduce pattern-matching until JavaScript had them natively, even though Babel applies the requisite transformations.

As a caveat, though, the more layers of indirection are pushed between your code and the machine that executes your code, the more potential there is for unexpected errors introduced by the various third parties. Suffice it to check GitHub issues, and your jaw drops, and you are just happy you didn’t have to pull out your hair in frustration that something didn’t work and you couldn’t pinpoint the issue.

All in all, the JavaScript ecosystem is highly volatile and fractured, with new patterns and approaches facilitating them pop up every so often. At the same time it is highly advanced in scope and depth. User interfaces are simply hard and evolving over time. The variety of end-user clients, be it a desktop or laptop browser, modern JavaScript-driven GUIs, or even Node.js backend code, contribute to the fragmentation of the ecosystem, with contradicting requirements either tearing the fabric that holds the numerous pieces together apart, or introducing but another layer on top of a subset of the ecosystem. The patchwork of frameworks and approaches is very confusing and results in QA deterioration across the board. Only the strict adoption of a particular framework and rigorous testing can help. The Node.js package manager (npm) along with the uncontrolled and unaudited practice of package management, where a developer blindly installs a package from the npm repository that was uploaded often by an unknown source, or with a different source this time (cf.\ the practice of ceding control of a browser extensions after its sale to some shady third party). The dependency management problem in npm is a story for a separate tome. But it’s similar to Java’s. A developer of a library that your product depends on may once decide to add another dependency, which itself pulls a whole tree of other dependencies into your project, and it’s not always obvious how to prune that tree, but your codebase suddenly deteriorates in benchmarks in the meantime, and you scramble for resources to revert that change imposed by a third party that you can’t control.

Quite a different story can be told about the PureScript world, though. It is very modularized, cf.\ Pursuit (PureScript’s official package repository like Haskell’s Hackage) and its type system, just like Haskell’s, provides a very high level of rigor that imposes certain limitations on what third-party code can do, being an integral part of the ecosystem. Of course, beyond the exponential type forall a. a -> a having the unique inhabitant id x = x, there are few cases where you can say with certainty what a type signature in the API entails about the function body. But you can be certain that if it is not executed in a MonadIO monadic context, you’re pretty safe, since the function is naturally pure. On a side note, in Haskell all functions are pure, with potential side-effects resulting in tainting by the monadic type constraints, such as IO, ReaderT, and so on, see my dedicated post on monads.

In the JavaScript world, certain established front-end frameworks provide tools, which facilitate development, testing, bundling, and deployment, and EDSLs, which formalize code and UI patterns, that each of them considers important.

For example,

  • Angular by Google, which is considered an enterprise-oriented framework, long embraced TypeScript,
  • React by Facebook, a start-up oriented framework, recently embraced TypeScript,
  • Vue, a community-driven upriser, also with TypeScript support,
  • Svelte, a simpler alternative to Vue, with very recent introducing of TypeScript support,
  • Stencil by Ionic, a web components framework, also typed for TypeScript,

provide means and tools for rapid development of PWAs, SPAs, and even native mobile apps, in particular using Ionic with the Capacitor project, Google’s Flutter, or React Native. Microsoft’s MAUI that is posed to replace Xamarin, is a promising, albeit very experimental as of writing, and will perhaps mature only in a couple of years time, provided Microsoft’s team behind it sticks to it---in the end, such corporate projects are good, as long as the company deems them viable, with many companies abruptly doing away even with tools popular outside the company. Corporate decisions must take into account all stakeholders.

As for TypeScript, it is no panacea. In fact, even if a framework provides types for its functions and classes, thereby enabling the use of TypeScript, you will have to rely on the correctness of that typing by a third party. Sometimes someone writes the code, another one declares the type signatures, and yet another person writes the documentation. Assuming they all communicate without friction, the result of that natural collaboration is likely to be good, but in reality there is always friction, even if it’s one and the same author. People make mistakes, and with more people, the errors grow superlinearly, perhaps exponentially, depending on the curbs of type systems and any QA process.

With Haskell and PureScript, and perhaps Elm, it’s the type system and the compiler that impose a minimal necessary degree of rigor that makes us root out a lot of bugs in the code. After all, the code must do what it claims to do. Sometimes astronomical stakes depend on that correctness to the spec.

Now, there’s a broad microbenchmark of front-end frameworks. Taking a closer look at the results and the code underlying the results, you can see that the more lightweight a framework, the better the result, in general, with Svelte and Elm leading the board among the more sophisticated ones. The results are obtained using Google’s Lighthouse benchmarking tool. It is appalling that .NET C# Blazor WebAssembly is among the least efficient frameworks, and Haskell’s Miso framework, which works by compiling Haskell directly to JavaScript is also not very efficient in certain aspects, being considerably slower than React/Redux, React/MobX, Angular, and many other ones, in “partial update” and “select row”. No wonder, the GHCJS compiler is still in its infancy, but still Miso’s results otherwise are impressive. It is however unfortunate that the statement of Miso devs regarding its benchmarking are at least ambiguous. First of all, JavaScript can also be considered a functional programming language. According to the screenshot, the results are dated. According to the link, halogen-0.4 has long overtaken miso-1.4. Current releases are versioned halogen-0.6.1 and miso-1.7.1. According to the respective release notes, halogen-0.5.0 introduced a significant performance improvement, which has not been taken into account in the benchmark comparison. However, all this is compared to Elm in the current release elm-compiler-0.19.1 of 2019-10-21. I will evaluate the three frameworks in due time and post my results here. But bear in mind that such microbenchmarks are in vain for production code, since in production a lot of validation must take place, which is often disabled by benchmark authors for bare comparisons. This is an instance of the …TODO effect in psychology, which describes the behavior of contestants when they know the rules, whereby they focus on optimization of the outcome; however, otherwise the evaluation is opaque.

But PureScript’s Halogen framework is almost on par with Angular and React, which is a fascinating and yet a bit surprising due to the huge amount of higher-order type system details. In this realm, however, Elm obviously dominates. If you look at the code, Halogen is of the most recent version, whereas Elm’s codebase is two years old. The microbenchmark itself does not let us draw general conclusions about the efficiency of the different frameworks, but you still can compare code complexity and see how simple it is to do such rather basic things. For example, you can see that Svelte is among the easiest and the most straightforward. Building complex apps is however a very different story---this is where code bloat results from.

A more realistic comparison can be found in the RealWorld App repository, with React, Angular, Vue, Elm, Svelte, and Halogen on PureScript leading in the GitHub popularity ranking. This will give a better benchmarking efficiency comparison. There is also React on PureScript among the popular choices.

The more lightweight a framework, the fewer validity checks are in place, but the more complex a framework, the more enterprise use cases are covered, the more correctness can be expected, but the more complex the architecture with many layers of indirection that lead to performance deterioration. However, when devising benchmarks, many such additional checks and indirections that are irrelevant to the bare comparison are often enabled by default. If you’re not an expert in a given framework, it may be hard to disable them to make the comparison representative. Otherwise it’s apples and oranges.

Generally speaking, if your use case is something production-ready and you want to use what’s already there and you believe that it will fit, you’re all set to go with something more advanced such as Angular. If the project is a lightweight application, Svelte may be the right choice, but you’ll have to extend it yourself where needed. For increased correctness, PureScript with Halogen is a great choice, alternatively perhaps Elm, but there have been complaints voiced regarding certain matters; moreover, Elm has not seen an update in almost two years soon, provided there’s a need for update, there are 200 open issues with no to very low activity, some open since 2015, so if you feel you may be affected, it is unlikely to have you covered in due time, even though there are 94 contributors as of writing, it is used by 8.3k projects. In comparison, PureScript has 173 contributors, it is used by 2.1k projects, PureScript’s Halogen …

Outro

I must admit, it has been a lot of fun. Modern front end development is a joy. And despite all the chaos in its ecosystem, choices can be made. This is in fact crucial.

Plans for the next couple of weeks:

  • Nitpicking on the design.
  • Make Haskell’s Sass library work with the Dart implementation that supports the more modern features.
  • Fix MathJax CDN errors due to the same-origin policy.
  • Fix MathJax CDN choice of fonts.
  • Improve the sidebar.
  • Implement dark-mode.
  • Implement sharing facilities.
  • Some quality posts.
  • Introduce D3.js data visualizations, including tag clouds and word cloud summaries.