It's 2025 and we still can't build websites
It's 2025 and we still don't have a clear sensible choice for building websites.
I mean, we can. But every person and their dog has a different way of skinning the beast; more technically said, every person has a different toolchain, which is a chain of tools from different layers (runtime, state-management, programming language, etc). New tools, and ways of chaining them together, are coming out all the time. It's no wonder it's hard to get started on web development, and so it's also no wonder that newcomers just pickup a package deal and cop its problems later on (I'm looking at you, Typescript React with the latest bundler of choice).
Let's walk through the layers and some of the tools within them.
- Runtime. How does the end user (their browser, or a mobile app) run our program?
- Javascript is the runtime language of choice for program logic, because it's best supported by browsers.
- WASM is an up and coming contender, but it's not well-supported on mobile, and its ecosystem is still stabilising (UI frameworks, state management).
- State management. How can we define the relationship between an "Increment" button and a text box which shows how many times we've clicked the button?
- React is the state management framework of choice, but it was not built with performance in mind. Apps which use React eventually scale up into slow beasts that are hard to optimise, and much like C, require a lot of deep knowledge to write performant code.
- Preact is one such performance-focused alternative.
- Svelte is another.
- Interleaved HTML programming. How can we conveniently define the structure of our app by writing HTML inside our program logic, and program logic inside HTML?
- JSX is Javascript interleaved with HTML, though "JSX" now refers to HTML interleaved with other languages too (like Svelte). It was popularised by React, and is an incredibly convenient way to write dynamic and template HTML.
- Interleaved HTML is also supported in OCaml through preprocessors (PPX). You can either use TyXML for pure OCaml applications, or ReasonML for OCaml applications which leverage Javascript packages.
- Type system. How can we get the computer to help us catch dumb (and not so dumb) errors before we run our code?
- Most people use Typescript, which compiles to Javascript. However, it's a weird language, is unsound (meaning it doesn't really catch all your errors), and can lead to extra pain when debugging due to its translation to Javascript. It also requires lots of type annotation (which is basically another programming language), because its type system is not able to infer them for you.
- The Hindley-Milner type system is much more sophisticated, discovered independently by mathematicians, logicians and computer scientists.
- Third-party packages. What code do I want to leverage that is already written by other people?
- NPM is a huge ecosystem of Javascript packages. It has many packages but they're not super trustworthy unless they're popular (because many users are required to find bugs across untyped codepaths).
- OCaml has a fantastic ecosystem. Some packages are very popular, but even niche packages are trustworthy because OCaml is a very safe language. The expressive type system also means that problems can be solved once and for all.
- Programming language. What language will we write our logic in?
- Javascript, Typescript, Rust, OCaml, Elm, Reason, Brainfuck (just kidding). Your choice of programming language will likely be chosen for you based on your other layer choices.
- OCaml is becoming increasingly popular for programming due to its fantastic type-system, great performance and support for object-oriented programming (a popular way of thinking). It has great inference (meaning the type-system can guess most of your types from your logic). It is based on the ML programming language.
Your choice of tool in each layer has implications for the available choices in the others. Given the overwhelming number of options, you might naively just build with the most popular toolchain choice, but this can lock you into problems. Choosing a more bespoke but sensible path might save you worlds of pain in debugging and performance optimisations later on.
The toolchain I am most excited for is as follows. I have ordered the layers by my chosen priorities.
- Type system. ML.
- Life is too short for debugging. Type systems are how humans teach a computer to sanity check our work, and how we can model our ideas. I would rather spend my time fighting a type-checker than debugging issues it missed in production. I would rather spend my time writing types that are difficult for a computer to infer rather than explicitly typing everything. My desires for strong inference and soundness mean Typescript is out the window forever. I would rather spend my learning as a programmer on more sophisticated type abstractions than logic patterns. ML is the only language that suits this.
- Programming language. OCaml with Reason.
- I would rather use object-oriented programming than only use pure functional programming. Haskell and Prolog are out, and OCaml is the natural choice for its popularity.
- Reason gives me JSX out of the box, and use efficient Javascript as my runtime through Melange. It also lets me port trusted Javascript packages into OCaml by writing bindings for them. I can start using React out of the box, and if I ever run into performance concerns, I can write bindings for the widely used performant alternative, Preact.
- Third-party packages. Reason permits me access to OCaml's package ecosystem (which I would prefer), and Javascript in a pinch through bindings.
- Interleaved HTML. I get JSX with Reason.
- State management. I'll start with React, but I'll likely move to Preact eventually.
- Runtime. Javascript, of course. Maybe when WASM has solid support on many devices and a great responsive UI ecosystem, I'll consider throwing away Javascript and the DOM.
Reason aims to succeed where Typescript has failed. OCaml can be used to write trustworthy performant code, and Melange can handle translation to performant Javascript and interop with trustworthy Javascript packages where OCaml packages fall short (or for runtime-specific packages).
I'm just another person with yet another toolchain. But hopefully the weight of this chain doesn't deplete my strength before I build applications I deem worth making.