<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/style.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>jakelazaroff.com</title><description>Just a programmer trying to make a home for myself on the WWW.</description><link>https://jakelazaroff.com/</link><item><title>An Interactive Intro to CRDTs</title><link>https://jakelazaroff.com/words/an-interactive-intro-to-crdts/</link><guid isPermaLink="true">https://jakelazaroff.com/words/an-interactive-intro-to-crdts/</guid><description>CRDTs don&apos;t have to be all academic papers and math jargon. Learn what CRDTs are and how they work through interactive visualizations and code samples.</description><pubDate>Wed, 04 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Scripts from &quot;./_Scripts.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Scripts /&amp;gt;&lt;/p&gt;
&lt;p&gt;Have you heard about CRDTs and wondered what they are? Maybe you&apos;ve looked into them a bit, but ran into a wall of academic papers and math jargon? That was me before I started my &lt;a href=&quot;https://www.recurse.com/&quot;&gt;Recurse Center&lt;/a&gt; batch. But I&apos;ve spent the past month or so doing research and writing code, and it turns out that you can build a lot with just a few simple things!&lt;/p&gt;
&lt;p&gt;In this two-part series, we&apos;ll learn what a CRDT is. Then we&apos;ll write a primitive CRDT, compose it into more complex data structures, and finally use what we&apos;ve learned to build a collaborative pixel art editor. All of this assumes no prior knowledge about CRDTs, and only a rudimentary knowledge of TypeScript.&lt;/p&gt;
&lt;p&gt;To pique your curiosity, this is what we&apos;ll end up with:&lt;/p&gt;
&lt;p&gt;&amp;lt;pixelart-demo resolution=&quot;100&quot; style=&quot;--accent: var(--color-primary)&quot;&amp;gt;&amp;lt;/pixelart-demo&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;noscript&amp;gt;
&amp;lt;p&amp;gt;JavaScript is required to run this demo.&amp;lt;/p&amp;gt;
&amp;lt;/noscript&amp;gt;&lt;/p&gt;
&lt;p&gt;Draw by clicking and dragging with your mouse. Change the paint color by using the color input on the bottom left. You can draw on either canvas and your changes will show up on the other, as if they were collaborating on the same picture.&lt;/p&gt;
&lt;p&gt;Clicking the network button prevents changes from reaching the other canvas (although they&apos;ll sync up again when they come back &quot;online&quot;). The latency slider adds a delay before changes on one canvas show up on the other.&lt;/p&gt;
&lt;p&gt;We&apos;ll build that in the next post. First, we need to learn about CRDTs!&lt;/p&gt;
&lt;h3&gt;What is a CRDT?&lt;/h3&gt;
&lt;p&gt;Okay, let&apos;s start from the top. CRDT stands for &quot;Conflict-free Replicated Data Type&quot;. That&apos;s a long acronym, but the concept isn&apos;t too complicated. It&apos;s a kind of data structure that can be stored on different computers (peers). Each peer can update its own state instantly, without a network request to check with other peers. Peers may have different states at different points in time, but are guaranteed to eventually converge on a single agreed-upon state. That makes CRDTs great for building rich collaborative apps, like Google Docs and Figma — without requiring a central server to sync changes.&lt;/p&gt;
&lt;p&gt;Broadly, there are two kinds of CRDTs: state-based and operation-based.&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; State-based CRDTs transmit their full state between peers, and a new state is obtained by merging all the states together. Operation-based CRDTs transmit only the actions that users take, which can be used to calculate a new state.&lt;/p&gt;
&lt;p&gt;That might make operation-based CRDTs sound way better. For example, if a user updates one item in a list, an operation-based CRDT can send a description of only that update, while a state-based CRDT has to send the whole list! The drawback is that operation-based CRDTs impose constraints on the communication channel: messages must be delivered exactly once, in causal order, to each peer.&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This post will exclusively focus on on state-based CRDTs. For brevity, I&apos;ll just say &quot;CRDTs&quot; from here on out, but know that I&apos;m referring specifically to state-based CRDTs.&lt;/p&gt;
&lt;p&gt;I&apos;ve been talking about what CRDTs do, but what &lt;strong&gt;is&lt;/strong&gt; a CRDT? Let&apos;s make it concrete: a CRDT is any data structure that implements this interface:&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface CRDT&amp;lt;T, S&amp;gt; {
  value: T;
  state: S;
  merge(state: S): void;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is to say, a CRDT contains at least three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A value, &lt;code&gt;T&lt;/code&gt;. This is the part the rest of our program cares about. The entire point of the CRDT is to reliably sync the value between peers.&lt;/li&gt;
&lt;li&gt;A state, &lt;code&gt;S&lt;/code&gt;. This is the metadata needed for peers to agree on the same value. To update other peers, the whole state is serialized and sent to them.&lt;/li&gt;
&lt;li&gt;A merge function. This is a function that takes some state (probably received from another peer) and merges it with the local state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The merge function must satisfy three properties to ensure that all peers arrive at the same result (I&apos;ll use the notation &lt;code&gt;A ∨ B&lt;/code&gt; to indicate merging state &lt;code&gt;A&lt;/code&gt; into state &lt;code&gt;B&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Commutativity: states can be merged in any order; &lt;code&gt;A ∨ B = B ∨ A&lt;/code&gt;. If Alice and Bob exchange states, they can each merge the other&apos;s state into their own and arrive at the same result.&lt;/li&gt;
&lt;li&gt;Associativity: when merging three (or more) states, it doesn&apos;t matter which are merged first; &lt;code&gt;(A ∨ B) ∨ C = A ∨ (B ∨ C)&lt;/code&gt;. If Alice receives states from both Bob and Carol, she can merge them into her own state in any order and the result will be the same.&lt;sup&gt;&lt;a href=&quot;#fn4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Idempotence: merging a state with itself doesn&apos;t change the state; &lt;code&gt;A ∨ A = A&lt;/code&gt;. If Alice merges her own state with itself, the result will be the same state she started with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mathematically proving that a merge function has all these properties might sound hard. But luckily, we don&apos;t have to do that! Instead, we can just combine CRDTs that already exist, leaning on the fact that someone has proven these things for us.&lt;/p&gt;
&lt;p&gt;Speaking of CRDTs that already exist: let&apos;s learn about one!&lt;/p&gt;
&lt;h3&gt;Last Write Wins Register&lt;/h3&gt;
&lt;p&gt;A register is a CRDT that holds a single value. There are a couple kinds of registers, but the simplest is the Last Write Wins Register (or LWW Register).&lt;/p&gt;
&lt;p&gt;LWW Registers, as the name suggests, simply overwrite their current value with the last value written. They determine which write occurred last using timestamps, represented here by integers that increment whenever the value is updated.&lt;sup&gt;&lt;a href=&quot;#fn5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; Here&apos;s the algorithm:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the received timestamp is less than the local timestamp, the register doesn&apos;t change its state.&lt;/li&gt;
&lt;li&gt;If the received timestamp is greater than the local timestamp, the register overwrites its local value with the received value. It also stores the received timestamp and some sort of identifier unique to the peer that last wrote the value (the peer ID).&lt;/li&gt;
&lt;li&gt;Ties are broken by comparing the local peer ID to the peer ID in the received state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try it out with the playground below.&lt;/p&gt;
&lt;p&gt;&amp;lt;lwwregister-demo style=&quot;--accent: var(--color-primary); --background: var(--color-background);&quot;&amp;gt;&amp;lt;/lwwregister-demo&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;noscript&amp;gt;
&amp;lt;p&amp;gt;JavaScript is required to run this demo.&amp;lt;/p&amp;gt;
&amp;lt;/noscript&amp;gt;&lt;/p&gt;
&lt;p&gt;Did you get a sense for how LWW Registers work? Here are a couple specific scenarios to try:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Turn the network off, make a bunch of updates to &lt;code&gt;bob&lt;/code&gt;, and then turn it back on. When you send updates from &lt;code&gt;alice&lt;/code&gt;, they&apos;ll be rejected until the timestamp exceeds &lt;code&gt;bob&lt;/code&gt;&apos;s timestamp.&lt;/li&gt;
&lt;li&gt;Perform the same setup, but once you turn the network back on, send an update from &lt;code&gt;bob&lt;/code&gt; to &lt;code&gt;alice&lt;/code&gt;. Notice how the timestamps are now synced up and &lt;code&gt;alice&lt;/code&gt; can write to &lt;code&gt;bob&lt;/code&gt; again!&lt;/li&gt;
&lt;li&gt;Increase the latency and send an update from both peers simultaneously. &lt;code&gt;alice&lt;/code&gt; will accept &lt;code&gt;bob&lt;/code&gt;&apos;s update, but &lt;code&gt;bob&lt;/code&gt; will reject &lt;code&gt;alice&lt;/code&gt;&apos;s. Since &lt;code&gt;bob&lt;/code&gt;&apos;s peer ID is greater, it breaks the timestamp tie.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&apos;s the code for the LWW Register:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class LWWRegister&amp;lt;T&amp;gt; {
  readonly id: string;
  state: [peer: string, timestamp: number, value: T];

  get value() {
    return this.state[2];
  }

  constructor(id: string, state: [string, number, T]) {
    this.id = id;
    this.state = state;
  }

  set(value: T) {
    // set the peer ID to the local ID, increment the local timestamp by 1 and set the value
    this.state = [this.id, this.state[1] + 1, value];
  }

  merge(state: [peer: string, timestamp: number, value: T]) {
    const [remotePeer, remoteTimestamp] = state;
    const [localPeer, localTimestamp] = this.state;

    // if the local timestamp is greater than the remote timestamp, discard the incoming value
    if (localTimestamp &amp;gt; remoteTimestamp) return;

    // if the timestamps are the same but the local peer ID is greater than the remote peer ID, discard the incoming value
    if (localTimestamp === remoteTimestamp &amp;amp;&amp;amp; localPeer &amp;gt; remotePeer) return;

    // otherwise, overwrite the local state with the remote state
    this.state = state;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s see how this stacks up to the CRDT interface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;state&lt;/code&gt; is a tuple of the peer ID that last wrote to the register, the timestamp of the last write and the value stored in the register.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt; is simply the last element of the &lt;code&gt;state&lt;/code&gt; tuple.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;merge&lt;/code&gt; is a method that implements the algorithm described above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LWW Registers have one more method named &lt;code&gt;set&lt;/code&gt;, which is called locally to set the register&apos;s value. It also updates the local metadata, recording the local peer ID as the last writer and incrementing the local timestamp by one.&lt;/p&gt;
&lt;p&gt;That&apos;s it! Although it appears simple, the humble LWW Register is a powerful building block with which we can create actual applications.&lt;/p&gt;
&lt;h3&gt;Last Write Wins Map&lt;/h3&gt;
&lt;p&gt;Most programs involve more than one value,&lt;sup&gt;&lt;a href=&quot;#fn6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt; which means we&apos;ll need a more complex CRDT than the LWW Register. The one we&apos;ll learn about today is called the Last Write Wins Map (or LWW Map).&lt;/p&gt;
&lt;p&gt;Let&apos;s start by defining a couple types. First, our value type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Value&amp;lt;T&amp;gt; = {
  [key: string]: T;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If each individual map value holds type &lt;code&gt;T&lt;/code&gt;, then the value of the entire LWW Map is a mapping of string keys to &lt;code&gt;T&lt;/code&gt; values.&lt;/p&gt;
&lt;p&gt;Here&apos;s our state type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type State&amp;lt;T&amp;gt; = {
  [key: string]: LWWRegister&amp;lt;T | null&amp;gt;[&quot;state&quot;];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you see the trick? From our application&apos;s perspective, the LWW Map just holds normal values — but &lt;strong&gt;it actually holds LWW Registers&lt;/strong&gt;. When we look at the full state, each key&apos;s state is the state of the LWW Register at that key.&lt;sup&gt;&lt;a href=&quot;#fn7&quot;&gt;[7]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I want to pause on that for a moment, because it&apos;s important. Composition lets us combine primitive CRDTs into more complex ones. When it&apos;s time to merge, all the parent does is pass slices of incoming state to the appropriate child&apos;s merge function. We can nest this process as many times as we want; each complex CRDT passing ever-smaller slices of state down to the next level, until we finally hit a primitive CRDT that performs the actual merge.&lt;/p&gt;
&lt;p&gt;From this perspective, the LWW Map merge function is simple: iterate through each key and hand off the incoming state at that key to the corresponding LWW Register to merge. Try it out in the playground below:&lt;/p&gt;
&lt;p&gt;&amp;lt;lwwmap-demo
add=&quot;off&quot;
delete=&quot;off&quot;
state=&apos;{ &quot;foo&quot;: [&quot;alice&quot;, 1, &quot;lorem ipsum&quot;], &quot;bar&quot;: [&quot;bob&quot;, 1, &quot;dolor sit amet&quot;] }&apos;
style=&quot;--accent: var(--color-primary); --background: var(--color-background);&quot;&amp;gt;&amp;lt;/lwwmap-demo&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;noscript&amp;gt;
&amp;lt;p&amp;gt;JavaScript is required to run this demo.&amp;lt;/p&amp;gt;
&amp;lt;/noscript&amp;gt;&lt;/p&gt;
&lt;p&gt;It&apos;s kind of difficult to trace what&apos;s happening here, so let&apos;s split up the state for each key. Note, though, that this is just a visualization aid; the full state is still being transmitted as a single unit.&lt;/p&gt;
&lt;p&gt;Try increasing the latency and then updating different keys on each peer. You&apos;ll see that each peer accepts the updated value with a higher timestamp, while rejecting the value with a lower timestamp.&lt;/p&gt;
&lt;p&gt;&amp;lt;lwwmap-demo
split
add=&quot;off&quot;
delete=&quot;off&quot;
state=&apos;{ &quot;foo&quot;: [&quot;alice&quot;, 1, &quot;lorem ipsum&quot;], &quot;bar&quot;: [&quot;bob&quot;, 1, &quot;dolor sit amet&quot;] }&apos;
style=&quot;--accent: var(--color-primary); --background: var(--color-background);&quot;&amp;gt;&amp;lt;/lwwmap-demo&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;noscript&amp;gt;
&amp;lt;p&amp;gt;JavaScript is required to run this demo.&amp;lt;/p&amp;gt;
&amp;lt;/noscript&amp;gt;&lt;/p&gt;
&lt;p&gt;The full LWW Map class is kinda beefy, so let&apos;s go through each property one by one. Here&apos;s the start of it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class LWWMap&amp;lt;T&amp;gt; {
  readonly id = &quot;&quot;;
  #data = new Map&amp;lt;string, LWWRegister&amp;lt;T | null&amp;gt;&amp;gt;();

  constructor(id: string, state: State&amp;lt;T&amp;gt;) {
    this.id = id;

    // create a new register for each key in the initial state
    for (const [key, register] of Object.entries(state)) {
      this.#data.set(key, new LWWRegister(this.id, register));
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;#data&lt;/code&gt; is a private property holding a map of the keys to LWW Register instances. To instantiate a LWW Map with preexisting state, we need to iterate through the state and instantiate each LWW Register.&lt;/p&gt;
&lt;p&gt;Remember, CRDTs need three properties: &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;merge&lt;/code&gt;. We&apos;ll look at &lt;code&gt;value&lt;/code&gt; first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  get value() {
    const value: Value&amp;lt;T&amp;gt; = {};

    // build up an object where each value is set to the value of the register at the corresponding key
    for (const [key, register] of this.#data.entries()) {
      if (register.value !== null) value[key] = register.value;
    }

    return value;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s a getter that iterates through the keys and gets each register&apos;s &lt;code&gt;value&lt;/code&gt;. As far as the rest of the app is concerned, it&apos;s just normal map!&lt;/p&gt;
&lt;p&gt;Now let&apos;s look at &lt;code&gt;state&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  get state() {
    const state: State&amp;lt;T&amp;gt; = {};

    // build up an object where each value is set to the full state of the register at the corresponding key
    for (const [key, register] of this.#data.entries()) {
      if (register) state[key] = register.state;
    }

    return state;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar to &lt;code&gt;value&lt;/code&gt;, it&apos;s a getter that builds up a map from each register&apos;s &lt;code&gt;state&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There&apos;s a clear trend here: iterating through the keys in &lt;code&gt;#data&lt;/code&gt; and handing things off to the register stored at that key. You&apos;d think &lt;code&gt;merge&lt;/code&gt; would work the same way, but it&apos;s a little more involved:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  merge(state: State&amp;lt;T&amp;gt;) {
    // recursively merge each key&apos;s register with the incoming state for that key
    for (const [key, remote] of Object.entries(state)) {
      const local = this.#data.get(key);

      // if the register already exists, merge it with the incoming state
      if (local) local.merge(remote);
      // otherwise, instantiate a new `LWWRegister` with the incoming state
      else this.#data.set(key, new LWWRegister(this.id, remote));
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we iterate through the incoming &lt;code&gt;state&lt;/code&gt; parameter rather than the local &lt;code&gt;#data&lt;/code&gt;. That&apos;s because if the incoming state is missing a key that &lt;code&gt;#data&lt;/code&gt; has, we know that we don&apos;t need to touch that key.&lt;sup&gt;&lt;a href=&quot;#fn8&quot;&gt;[8]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;For each key in the incoming state, we get the local register at that key. If we find one, the peer is &lt;strong&gt;updating&lt;/strong&gt; an existing key that we already know about, so we call that register&apos;s &lt;code&gt;merge&lt;/code&gt; method with the incoming state at that key. Otherwise, the peer has &lt;strong&gt;added&lt;/strong&gt; a new key to the map, so we instantiate a new LWW Register using the incoming state at that key.&lt;/p&gt;
&lt;p&gt;In addition to the CRDT methods, we need to implement methods more commonly found on maps: &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt; and &lt;code&gt;has&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s start with &lt;code&gt;set&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  set(key: string, value: T) {
    // get the register at the given key
    const register = this.#data.get(key);

    // if the register already exists, set the value
    if (register) register.set(value);
    // otherwise, instantiate a new `LWWRegister` with the value
    else this.#data.set(key, new LWWRegister(this.id, [this.id, 1, value]));
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like in the merge method, we&apos;re either calling the register&apos;s &lt;code&gt;set&lt;/code&gt; to update an existing key, or instantiating a new LWW Register to add a new key. The initial state uses the local peer ID, a timestamp of 1 and the value passed to &lt;code&gt;set&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;get&lt;/code&gt; is even simpler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  get(key: string) {
    return this.#data.get(key)?.value ?? undefined;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Get the register from the local map, and return its value if it has one.&lt;/p&gt;
&lt;p&gt;Why coalesce to &lt;code&gt;undefined&lt;/code&gt;? Because each register holds &lt;code&gt;T | null&lt;/code&gt;. And with the &lt;code&gt;delete&lt;/code&gt; method, we&apos;re ready to explain why:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  delete(key: string) {
    // set the register to null, if it exists
    this.#data.get(key)?.set(null);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rather than fully removing the key from the map, we set the register value to &lt;code&gt;null&lt;/code&gt;. The metadata is kept around so we can disambiguate deletions from states that simply don&apos;t have a key yet. These are called &lt;strong&gt;tombstones&lt;/strong&gt; — the ghosts of CRDTs past.&lt;/p&gt;
&lt;p&gt;Consider what would happen if we really did delete the keys from the map, rather than leaving a tombstone. Here&apos;s a playground where peers can add keys, but not delete them. Can you figure out how to get a peer to delete a key?&lt;/p&gt;
&lt;p&gt;&amp;lt;lwwmap-demo
delete=&quot;off&quot;
merge=&quot;naive&quot;
state=&apos;{ &quot;foo&quot;: [&quot;alice&quot;, 1, &quot;lorem ipsum&quot;] }&apos;
style=&quot;--accent: var(--color-primary); --background: var(--color-background);&quot;&amp;gt;&amp;lt;/lwwmap-demo&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;noscript&amp;gt;
&amp;lt;p&amp;gt;JavaScript is required to run this demo.&amp;lt;/p&amp;gt;
&amp;lt;/noscript&amp;gt;&lt;/p&gt;
&lt;p&gt;Turn off the network, add a key to &lt;code&gt;alice&lt;/code&gt;&apos;s map, then turn the network back on. Finally, make a change to &lt;code&gt;bob&lt;/code&gt;&apos;s map. Since &lt;code&gt;alice&lt;/code&gt; sees that the incoming state from &lt;code&gt;bob&lt;/code&gt; is missing that key, she removes it from her own state — even though &lt;code&gt;bob&lt;/code&gt; never knew about that key in the first place. Whoops!&lt;/p&gt;
&lt;p&gt;Here&apos;s a playground with the correct behavior. You can also see what happens when a key is deleted.&lt;/p&gt;
&lt;p&gt;&amp;lt;lwwmap-demo
state=&apos;{ &quot;foo&quot;: [&quot;alice&quot;, 1, &quot;lorem ipsum&quot;] }&apos;
style=&quot;--accent: var(--color-primary); --background: var(--color-background);&quot;&amp;gt;&amp;lt;/lwwmap-demo&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;noscript&amp;gt;
&amp;lt;p&amp;gt;JavaScript is required to run this demo.&amp;lt;/p&amp;gt;
&amp;lt;/noscript&amp;gt;&lt;/p&gt;
&lt;p&gt;Notice how we never remove deleted keys from the map. This is one drawback to CRDTs — we can only ever add information, not remove it. Although from the application&apos;s perspective the key has been fully deleted, the underlying state still records that the key was once there. In technical terms, we say that CRDTs are &lt;strong&gt;monotonically increasing&lt;/strong&gt; data structures.&lt;sup&gt;&lt;a href=&quot;#fn9&quot;&gt;[9]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The final LWW Map method is &lt;code&gt;has&lt;/code&gt;, which returns a boolean indicating whether the map contains a given key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  has(key: string) {
    // if a register doesn&apos;t exist or its value is null, the map doesn&apos;t contain the key
    return this.#data.get(key)?.value !== null;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s a special case here: if the map contains a register at the given key, but the register contains &lt;code&gt;null&lt;/code&gt;, the map is considered to not contain the key.&lt;/p&gt;
&lt;p&gt;For posterity, here&apos;s the full LWW Map code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class LWWMap&amp;lt;T&amp;gt; {
  readonly id: string;
  #data = new Map&amp;lt;string, LWWRegister&amp;lt;T | null&amp;gt;&amp;gt;();

  constructor(id: string, state: State&amp;lt;T&amp;gt;) {
    this.id = id;

    // create a new register for each key in the initial state
    for (const [key, register] of Object.entries(state)) {
      this.#data.set(key, new LWWRegister(this.id, register));
    }
  }

  get value() {
    const value: Value&amp;lt;T&amp;gt; = {};

    // build up an object where each value is set to the value of the register at the corresponding key
    for (const [key, register] of this.#data.entries()) {
      if (register.value !== null) value[key] = register.value;
    }

    return value;
  }

  get state() {
    const state: State&amp;lt;T&amp;gt; = {};

    // build up an object where each value is set to the full state of the register at the corresponding key
    for (const [key, register] of this.#data.entries()) {
      if (register) state[key] = register.state;
    }

    return state;
  }

  has(key: string) {
    return this.#data.get(key)?.value !== null;
  }

  get(key: string) {
    return this.#data.get(key)?.value;
  }

  set(key: string, value: T) {
    // get the register at the given key
    const register = this.#data.get(key);

    // if the register already exists, set the value
    if (register) register.set(value);
    // otherwise, instantiate a new `LWWRegister` with the value
    else this.#data.set(key, new LWWRegister(this.id, [this.id, 1, value]));
  }

  delete(key: string) {
    // set the register to null, if it exists
    this.#data.get(key)?.set(null);
  }

  merge(state: State&amp;lt;T&amp;gt;) {
    // recursively merge each key&apos;s register with the incoming state for that key
    for (const [key, remote] of Object.entries(state)) {
      const local = this.#data.get(key);

      // if the register already exists, merge it with the incoming state
      if (local) local.merge(remote);
      // otherwise, instantiate a new `LWWRegister` with the incoming state
      else this.#data.set(key, new LWWRegister(this.id, remote));
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Next Steps&lt;/h3&gt;
&lt;p&gt;If you&apos;re reading this now, we&apos;re in the short period when this post has been published, but the next one has not. &lt;a href=&quot;/rss.xml&quot;&gt;Subscribe to my RSS feed&lt;/a&gt; to be notified when it&apos;s ready!&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;They&apos;re also known as CvRDTs (&quot;Cv&quot; standing for &quot;convergent&quot;) and CmRDTs (&quot;Cm&quot; standing for &quot;commutative&quot;), respectively, although I think &quot;state-based&quot; and &quot;operation-based&quot; are the preferred terms. &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are also delta CRDTs, or hybrid CRDTs, which allow peers to negotiate the subset of the state they need to send each other. That&apos;s one example of blending operation-based and state-based CRDTs. But the fundamental tradeoff remains: sending less data between peers means more constraints on communication. &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technically, a CRDT can be anything that follows the merging rules described below. This is a working definition; in practice, implementations in object-oriented languages will end up looking something like this. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commutativity and associativity might sound the same, and indeed most commutative operations are also associative. But there are a few math operations that are only one or the other. Matrix multiplication, for example, is &lt;a href=&quot;https://en.wikipedia.org/wiki/Matrix_multiplication#Non-commutativity&quot;&gt;associative but not commutative&lt;/a&gt;. And surprisingly, floating point arithmetic — i.e. any math operator in JavaScript — &lt;a href=&quot;https://en.wikipedia.org/wiki/Associative_property#Nonassociativity_of_floating_point_calculation&quot;&gt;is commutative but not associative&lt;/a&gt;! &lt;a href=&quot;#fnref4&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You might ask: why not use the actual time? Unfortunately, accurately syncing clocks between two computers is an extremely hard problem. Using incrementing integers like this is one simple version of a &lt;a href=&quot;https://en.wikipedia.org/wiki/Logical_clock&quot;&gt;logical clock&lt;/a&gt;, which captures the order of events relative to each other rather than to the &quot;wall clock&quot;. &lt;a href=&quot;#fnref5&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;[citation needed] &lt;a href=&quot;#fnref6&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the value type is &lt;code&gt;T&lt;/code&gt;, why is the state type a union of &lt;code&gt;T | null&lt;/code&gt;? More on that in a bit! &lt;a href=&quot;#fnref7&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You might be wondering: if another peer deleted a key from their map, shouldn&apos;t we remove it from our local map as well? It&apos;s the same reason the state holds &lt;code&gt;T | null&lt;/code&gt;. We&apos;re getting there! &lt;a href=&quot;#fnref8&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are a couple ways mitigate this drawback, both of which are outside the scope of this article. One is &quot;garbage collection&quot;: pruning tombstones from CRDTs, which prevents you from merging states with any changes made before the tombstones were removed. Another is creating an efficient format to encode the data. You can also combine these methods. Research suggests that &lt;a href=&quot;https://youtu.be/x7drE24geUw?t=3587&quot;&gt;this can result in as little as 50% overhead compared to the &quot;plain&quot; data&lt;/a&gt;. &lt;a href=&quot;#fnref9&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>Building a Live Coding Audio Playground</title><link>https://jakelazaroff.com/words/building-a-live-coding-audio-playground/</link><guid isPermaLink="true">https://jakelazaroff.com/words/building-a-live-coding-audio-playground/</guid><description>Two weeks at Recurse went by fast! I started out trying to build an Audio Units extension and — in a classic yak shave — ended up building a live coding audio playground.</description><pubDate>Mon, 21 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two weeks at Recurse Center went by fast! I started out trying to build an Audio Units extension and — in a classic &lt;a href=&quot;https://xkcd.com/1739/&quot;&gt;yak shave&lt;/a&gt; — ended up building a live coding audio playground. (I have not yet finished the original project.)&lt;/p&gt;
&lt;p&gt;If you just want to play around with the app: &lt;a href=&quot;https://fxplayground.pages.dev&quot;&gt;here it is&lt;/a&gt;. If you want to hear how I made it, then keep reading!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;My initial goal at RC was to build an &lt;a href=&quot;https://en.wikipedia.org/wiki/Audio_Units&quot;&gt;Audio Units&lt;/a&gt; (AU) extension. AU is Apple&apos;s audio plugin system, which works with apps like Logic and GarageBand. There are other plugin systems,&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; but I use Logic when recording audio for myself and for &lt;a href=&quot;https://babygotbacktalk.bandcamp.com&quot;&gt;my band&lt;/a&gt;, so I wanted to try extending it.&lt;/p&gt;
&lt;p&gt;Apple actually has a &lt;a href=&quot;https://developer.apple.com/documentation/avfaudio/audio_engine/audio_units/creating_an_audio_unit_extension&quot;&gt;tutorial on creating an AU extension&lt;/a&gt;, complete with a good starter template. Almost &lt;strong&gt;too&lt;/strong&gt; good — it gives you a finished (albeit basic) AU extension, ready to compile and run. Great for getting up and running quickly, but bad for building a mental model of how it works.&lt;/p&gt;
&lt;p&gt;I tend to learn by exploring, so I decided to try building a low-pass filter. And let me tell you: building an audio effect in Xcode is like pulling teeth.&lt;/p&gt;
&lt;p&gt;I mean, okay. It&apos;s not &lt;strong&gt;that&lt;/strong&gt; bad. But I&apos;m a disciple of the &lt;a href=&quot;https://www.youtube.com/watch?v=PUv66718DII&quot;&gt;Bret Victor school of thought&lt;/a&gt;, where trying to create something without immediate feedback is like working without one of your senses. So I decided to build an environment in which I could get that immediate feedback!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The rough plan: let users upload audio, provide a text field in which to write their effect, and display a visualization of both the original audio signal (&quot;dry&quot;) and the audio signal modified by the user&apos;s code (&quot;wet&quot;). A perfect fit for a small single-page app!&lt;/p&gt;
&lt;p&gt;I built the app using &lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;. I know I wrote before about &lt;a href=&quot;https://jakelazaroff.com/words/no-one-ever-got-fired-for-choosing-react/&quot;&gt;defaulting to React for new projects&lt;/a&gt;, but what is even the point of being at RC if not to try new things? Plus, the design I had planned didn&apos;t involve any rich widgets,&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; so I wouldn&apos;t need any component libraries — almost all the complexity was in the audio code.&lt;/p&gt;
&lt;p&gt;Some more assorted thoughts about Svelte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single-file components are such a pleasant authoring experience! I never want to build an app without them. Someone please bring them to React.&lt;/li&gt;
&lt;li&gt;I&apos;m wary of two-way binding after years of working with Angular 1, but it&apos;s really convenient to be able to just define functions within a component without the rigamarole of &lt;code&gt;useImperativeHandle&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;useEffect&lt;/code&gt;, my most hated hook! &lt;a href=&quot;https://learn.svelte.dev/tutorial/reactive-statements&quot;&gt;Reactive blocks&lt;/a&gt; are way less error-prone, although the lack of a &quot;cleanup function&quot; definitely made my life hard for a bit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately, I really enjoyed Svelte! I&apos;d still be hesitant to build a large app with it, but I&apos;ll definitely be using it again for smaller side projects like this.&lt;/p&gt;
&lt;p&gt;Anyway, back to the audio code: the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API&quot;&gt;Web Audio API&lt;/a&gt; is pretty incredible. It lets you construct a full audio routing graph, with oscillators and visualizers and all sorts of processing nodes. The secret sauce here is the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet&quot;&gt;&lt;code&gt;AudioWorklet&lt;/code&gt;&lt;/a&gt;, which lets you run audio processing code off the main thread.&lt;/p&gt;
&lt;p&gt;An audio worklet has two parts: the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor&quot;&gt;&lt;code&gt;AudioWorkletProcessor&lt;/code&gt;&lt;/a&gt;, which is where you write your audio processing code, and an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletNode&quot;&gt;&lt;code&gt;AudioWorkletNode&lt;/code&gt;&lt;/a&gt;, which is an object that exists in the main thread and gets connected to the rest of your audio routing graph. So basically, your UI code interacts with the &lt;code&gt;AudioWorkletNode&lt;/code&gt;, but the &lt;code&gt;AudioWorkletProcessor&lt;/code&gt; is doing the real work &quot;behind the scenes&quot;.&lt;/p&gt;
&lt;p&gt;At a high level, here&apos;s how the playground works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user types code in the editor.&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;That code gets compiled into an &lt;code&gt;AudioWorkletProcessor&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The app creates a corresponding &lt;code&gt;AudioWorkletNode&lt;/code&gt; and connects it to the audio routing graph.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Making an audio worklet should be fairly simple: create a separate JS file with a class extending &lt;code&gt;AudioWorkletProcessor&lt;/code&gt;, create an &lt;code&gt;AudioContext&lt;/code&gt; and call its &lt;code&gt;addModule&lt;/code&gt; method with that file&apos;s URL, then create an &lt;code&gt;AudioWorkletNode&lt;/code&gt; with the name of that processor.&lt;sup&gt;&lt;a href=&quot;#fn4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; That&apos;s all well and good, but the JS file doesn&apos;t actually exist — it&apos;s based on user-supplied code. I had a hunch, though, that I could fake it with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static&quot;&gt;&lt;code&gt;URL.createObjectURL&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That hunch turned out to be correct! If this app has a beating heart, it&apos;s this &lt;code&gt;compile&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let n = 0n;

async function compile(ctx: AudioContext, code: string) {
  // each AudioWorkletProcessor needs a unique name
  const name = `${++n}`;

  // the source code of the AudioWorkletProcessor module
  const src = `
    class Filter extends AudioWorkletProcessor {
      process(inputs, outputs, params) {
        ${code}
      }
    }

    registerProcessor(${name}, Filter);
  `;

  // create a fake JS file from the source code
  const file = new File([src], &quot;filter.js&quot;);

  // create a URL for the fake JS file
  const url = URL.createObjectURL(file.slice(0, file.size, &quot;application/javascript&quot;));

  // add the fake JS file as an AudioWorkletProcessor
  await ctx.audioWorklet.addModule(url);

  // revoke the URL so as to not leak memory
  URL.revokeObjectURL(url);

  // create a new AudioWorkletNode from the AudioWorkletProcessor registered with `name`
  return new AudioWorkletNode(ctx, name);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yup, you&apos;re reading right: most of it is just a big ol&apos; hardcoded string of JS code! The user&apos;s code gets interpolated into the &lt;code&gt;process&lt;/code&gt; method body, which gets called repeatedly as audio flows through the graph. The &lt;code&gt;compile&lt;/code&gt; function runs in a Svelte reactive block, so it creates a new &lt;code&gt;AudioWorkletNode&lt;/code&gt; whenever the user changes the code. That node then gets connected to the larger audio routing graph. The graph is still pretty small, though: &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag to &lt;code&gt;MediaElementAudioSourceNode&lt;/code&gt; to &lt;code&gt;AudioWorkletNode&lt;/code&gt; to the &lt;code&gt;AudioContext&lt;/code&gt; destination.&lt;/p&gt;
&lt;p&gt;If you&apos;ve tried the app already, you might be wondering about the parameters — the table with the sliders on the bottom right. Those are actually part of the audio worklet spec! You can define a static getter on your &lt;code&gt;AudioWorkletProcessor&lt;/code&gt; subclass called &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/parameterDescriptors&quot;&gt;&lt;code&gt;parameterDescriptors&lt;/code&gt;&lt;/a&gt; that lets you configure the processor from the main thread. Letting the user tweak them as sliders is important because they can really experiment with their effects: rather than having to type in different numbers, they can get instant feedback on a range of values.&lt;/p&gt;
&lt;p&gt;Here&apos;s how that fits into the &lt;code&gt;compile&lt;/code&gt; function (omitting code from the previous example):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface Parameter {
  defaultValue: number;
  minValue: number;
  maxValue: number;
  name: string;
  automationRate: &quot;k-rate&quot;;
}

async function compile(ctx: AudioContext, code: string, params: Parameter[]) {
  // ...

  const src = `
    class Filter extends AudioWorkletProcessor {
      process(inputs, outputs, params) {
        ${code}
      }

      static get parameterDescriptors() {
        return ${JSON.stringify(params)};
      }
    }

    registerProcessor(${name}, Filter);
  `;

  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Parameter&lt;/code&gt; data structure is exactly what the &lt;code&gt;AudioWorkletProcessor&lt;/code&gt; expects to be returned from &lt;code&gt;parameterDescriptors&lt;/code&gt;!&lt;sup&gt;&lt;a href=&quot;#fn5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; The array just needs to be stringified to JSON so as not to return &lt;code&gt;[Object object]&lt;/code&gt;, which would be invalid JS.&lt;/p&gt;
&lt;p&gt;Initially, the slider just controled the &lt;code&gt;defaultValue&lt;/code&gt; property, and the whole audio worklet was recreated whenever it changes. The performance seemed fine, since the generated JS file is loaded from memory, but it had the annoying side effect of removing any state set in the worklet class. That made the slider much less useful for anything requiring more than a frame or so of state, since state wouldn&apos;t be retained as the slider moved. Ultimately, I just set &lt;code&gt;defaultValue&lt;/code&gt; to the average of &lt;code&gt;minValue&lt;/code&gt; and &lt;code&gt;maxValue&lt;/code&gt; and made the slider call the parameter&apos;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setValueAtTime&quot;&gt;&lt;code&gt;setValueAtTime&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;
&lt;p&gt;In addition to hearing the effect, the app also visualizes it so the user can see how their filtered signal compares to the original one. This is where the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode&quot;&gt;&lt;code&gt;AnalyserNode&lt;/code&gt;&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;#fn6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt; comes in: it takes a &lt;a href=&quot;https://en.wikipedia.org/wiki/Fast_Fourier_transform&quot;&gt;Fast Fourier Transform&lt;/a&gt; of the audio, so I can plot the frequencies as a graph. This is particularly important for effects like equalizers, where the point of the effect is to amplify or attenuate certain frequencies compared to the dry signal.&lt;/p&gt;
&lt;p&gt;Did you click on that link? Then you found the share feature! It just serializes the code and parameters to the URL. When you load the page, if it detects the right query string, it pre-populates everything from the URL. That way, you can send a link to your friends, and they can play around with the effects you&apos;ve made.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;What next? Although there are a ton of features I&apos;ve thought of — logging from user code, logarithmic frequency binning for the visualization, support for languages other than JS — I think I&apos;m going to move onto something else at RC. The point of the program is to learn, and I&apos;ve learned most of what I set out to when I built this thing. I tried out Svelte, &lt;code&gt;AudioWorklet&lt;/code&gt;s, the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, figured out a method for incorporating user-generated code and learned how to build a simple low-pass filter.&lt;/p&gt;
&lt;p&gt;I&apos;ll probably come back to this later (or sooner, if you try it out and &lt;a href=&quot;https://mastodon.social/@jakelazaroff&quot;&gt;let me know what you think&lt;/a&gt;). For now, stay tuned for more RC projects!&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The main one is &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_Studio_Technology&quot;&gt;VST&lt;/a&gt;, which was developed by Steinberg and is used by basically everyone else. &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Well, I did need modals for help text. But luckily, the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot;&gt;native &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element&lt;/a&gt; is well supported! &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A plain &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; is not a great experience for exiting code. I used &lt;a href=&quot;https://codemirror.net/&quot;&gt;CodeMirror&lt;/a&gt;, which seems to strike a nice balance between lightweight and full-featured. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I&apos;m skipping some steps here for brevity. MDN has &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor#processing_audio&quot;&gt;more complete documentation&lt;/a&gt;. &lt;a href=&quot;#fnref4&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you read the documentation for audio worklet parameters, you may wondering why the &lt;code&gt;automationRate&lt;/code&gt; is type &lt;code&gt;&quot;k-rate&quot;&lt;/code&gt;, rather than &lt;code&gt;&quot;a-rate&quot; | &quot;k-rate&quot;&lt;/code&gt;. &lt;code&gt;a-rate&lt;/code&gt; is used when the value of a parameter varies over time, whereas &lt;code&gt;k-rate&lt;/code&gt; is used when it stays the same. For the playground, parameters only ever have one value, so I don&apos;t need to worry about &lt;code&gt;a-rate&lt;/code&gt;. &lt;a href=&quot;#fnref5&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I guess I can&apos;t really complain about this, because so much of the world accommodates American English where we don&apos;t deserve it, but I do wish it were spelled &lt;code&gt;AnalyzerNode&lt;/code&gt; (with a &quot;z&quot;) (pronounced &quot;zee&quot;, not &quot;zed&quot;) (I&apos;ll stop now). &lt;a href=&quot;#fnref6&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>Starting at Recurse Center</title><link>https://jakelazaroff.com/words/starting-at-recurse-center/</link><guid isPermaLink="true">https://jakelazaroff.com/words/starting-at-recurse-center/</guid><description>Breaking character for a life update: I&apos;m doing a batch at the Recurse Center!</description><pubDate>Mon, 07 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Breaking character for a life update: I&apos;m doing a batch at the &lt;a href=&quot;https://recurse.com&quot;&gt;Recurse Center&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I&apos;ve been doing web development for &lt;a href=&quot;https://jake.museum&quot;&gt;a long time&lt;/a&gt;. I started making websites for myself in middle school. In college, I did a couple of web development internships, as of summer 2023 I&apos;ve been a professional web developer for just over 11 years.&lt;/p&gt;
&lt;p&gt;Most of it has been related to frontend. As time went on, I started working in other related areas, such as backend and infrastructure. I&apos;ve been fortunate enough to work at companies that encouraged me to develop T-shaped skills — a deep specialization in one area and broad knowledge in others.&lt;/p&gt;
&lt;p&gt;Hopefully, RC gives me a chance to pursue some other interests outside of that — at least, in a way that&apos;s a little more directed than just fumbling around for tutorials on Google. Not sure how often I plan to write about it while I&apos;m there, but I do hope to make some of my work public, and you can look forward to at least a recap post when I&apos;m done.&lt;/p&gt;
&lt;p&gt;By the way, posts here about RC will have this link at the bottom encouraging you to join. If any of this sounds fun or interesting to you, check it out!&lt;/p&gt;
</content:encoded></item><item><title>An Extremely Simple React Starter Kit</title><link>https://jakelazaroff.com/words/an-extremely-simple-react-starter-kit/</link><guid isPermaLink="true">https://jakelazaroff.com/words/an-extremely-simple-react-starter-kit/</guid><description>If you’re worried about complexity in the JavaScript ecosystem, you might be sleeping on one of my favorite web development tools: esbuild!</description><pubDate>Fri, 21 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s face it: the modern JavaScript ecosystem has a reputation for complexity. People feel like best practices and popular tools change out from underneath them, there are too many layers of dependencies and &lt;a href=&quot;https://xkcd.com/2347/&quot;&gt;everything is perpetually on the brink of collapse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the record, I think that take lacks nuance. We expect more out of web apps than ever before: real-time updates and collaboration, fancy animations, interactive widgets like popovers and nested dropdown menus — and accessible and performant versions of all of the above.&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; The ecosystem is complex because building UIs is complex, and if we push down complexity in one area &lt;a href=&quot;https://en.wikipedia.org/wiki/Waterbed_theory&quot;&gt;it’ll probably just pop up in another&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Still, though — the point is well taken. When I want to quickly try out an idea, &lt;a href=&quot;https://jakelazaroff.com/words/no-one-ever-got-fired-for-choosing-react/&quot;&gt;time spent on tooling is time wasted&lt;/a&gt;. I don’t want to have to deal with a bunch of configuration or read up on changes to a big framework or make sure different dependencies play well together. I want something boring that’s easy to spin up with as few moving parts as possible.&lt;/p&gt;
&lt;p&gt;That’s why I feel compelled to write about one of my favorite web development tools: &lt;a href=&quot;https://esbuild.github.io&quot;&gt;&lt;strong&gt;esbuild&lt;/strong&gt;&lt;/a&gt;! It’s a batteries-included JS and CSS bundler made by &lt;a href=&quot;https://madebyevan.com/&quot;&gt;Evan Wallace&lt;/a&gt;, co-founder and former CTO of Figma. Written in Go rather than JavaScript, it’s &lt;a href=&quot;https://esbuild.github.io/faq/#benchmark-details&quot;&gt;ridiculously fast&lt;/a&gt;, and it powers some &lt;a href=&quot;https://vitejs.dev/guide/dep-pre-bundling.html&quot;&gt;other popular bundlers you may have heard of&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Goals&lt;/h3&gt;
&lt;p&gt;What features do we want when building a web app? Here’s my opinionated list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A development server with live reload:&lt;/strong&gt; I work significantly faster when the browser is already up-to-date with my latest changes by the time I look over from my text editor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript and CSS bundling:&lt;/strong&gt; This lets me structure files however I want, since the tooling will combine them to produce the final output that runs in the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Syntax lowering:&lt;/strong&gt; There are a lot of cool new JavaScript and CSS features, and it’s nice to be able to use them before they’re widely supported.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript support:&lt;/strong&gt; This is a must for me, but if you don’t like TypeScript just forget I said anything!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSS modularity:&lt;/strong&gt; It’s convenient if the styles in different components are isolated from each other. Some people use Tailwind for this, but &lt;a href=&quot;https://jakelazaroff.com/words/tailwind-is-a-leaky-abstraction/&quot;&gt;I don’t like it&lt;/a&gt;. My preferred way to solve this problem is &lt;a href=&quot;https://github.com/css-modules/css-modules&quot;&gt;CSS modules&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed:&lt;/strong&gt; I suppose this is a nice-to-have, but development is so much more efficient (and fun!) when there’s a quick feedback loop between making a change and seeing the results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using something like Next.js will get you all that and a lot more — but it’s &lt;strong&gt;much&lt;/strong&gt; heavier. According to NPM, Next.js has &lt;a href=&quot;https://www.npmjs.com/package/next?activeTab=dependencies&quot;&gt;25 dependencies&lt;/a&gt;, and Packagephobia reports a &lt;a href=&quot;https://packagephobia.com/result?p=next&quot;&gt;170MB install size&lt;/a&gt;. esbuild has one dependency,&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; and adds a comparatively tiny &lt;a href=&quot;https://packagephobia.com/result?p=esbuild&quot;&gt;9MB&lt;/a&gt; to your &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;I should also mention that this list is for building a single-page app. There’s been a lot of debate around those lately, so let me reiterate: &lt;strong&gt;if you’re building a serious production app, you should probably use a framework that lets you render HTML on the server&lt;/strong&gt;. This is about starting prototypes and fun projects quickly while keeping tooling to a bare minimum.&lt;/p&gt;
&lt;p&gt;So why am I writing this now? As of a few days ago, &lt;a href=&quot;https://github.com/evanw/esbuild/releases/tag/v0.18.14&quot;&gt;esbuild added partial CSS module support&lt;/a&gt;. Before that, esbuild checked every one of those items except for CSS modularity; now, it’s got everything we need out of the box.&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3&gt;Project Structure&lt;/h3&gt;
&lt;p&gt;We’ll go over a barebones example React app just to showcase the different features mentioned above. Here’s what the directory structure looks like:&lt;sup&gt;&lt;a href=&quot;#fn4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
├─ components/
│  ├─ App.module.css
│  ╰─ App.tsx
├─ index.html
├─ index.tsx
├─ livereload.js
├─ style.css
╰─ types.d.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; is pretty straightforward: it sets up a basic HTML document, links to the bundled CSS and JS files and has an empty &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; for the React app to render into. Note that even though the global stylesheet in our source code is named &lt;code&gt;style.css&lt;/code&gt;, we’re linking to &lt;code&gt;index.css&lt;/code&gt; — esbuild names the bundled CSS file based on the name of the corresponding JS entrypoint.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/index.css&quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;app&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script src=&quot;/index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;index.tsx&lt;/code&gt; is similarly spartan. It has two jobs: import any global styles, and render the root React component.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;./style.css&quot;;

import { createRoot } from &quot;react-dom/client&quot;;

import App from &quot;./components/App&quot;;

const app = document.querySelector(&quot;#app&quot;);
if (app) createRoot(app).render(&amp;lt;App /&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Global styles go in &lt;code&gt;style.css&lt;/code&gt;. The selectors in there won’t be changed, so that’s a good place to put things that apply to the whole project: fonts, &lt;a href=&quot;https://www.joshwcomeau.com/css/custom-css-reset/&quot;&gt;CSS reset&lt;/a&gt;, custom properties, base styles, etc. You can also &lt;code&gt;@import&lt;/code&gt; other stylesheets and they&apos;ll be bundled along with it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@import &quot;./reset.css&quot;;

body {
  font-family: sans-serif;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt; is the root component. The project layout at this point is kinda arbitrary; this is mostly a contrived example to show off CSS modules.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import css from &quot;./App.module.css&quot;;

export default function App() {
  return &amp;lt;h1 className={css.wrapper}&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Speaking of CSS modules, did you notice &lt;code&gt;types.d.ts&lt;/code&gt;? That’s there because TypeScript doesn’t know how to type check &lt;code&gt;.module.css&lt;/code&gt; files (so you can skip this section if you’re not using TypeScript). You need to tell it the type of the import:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;declare module &quot;*.module.css&quot; {
  const map: { [key: string]: string };
  export default map;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, there’s &lt;code&gt;livereload.js&lt;/code&gt;, which will only be included in development. It (surprise!) reloads the page whenever the esbuild server rebuilds any files. This snippet is straight from the &lt;a href=&quot;https://esbuild.github.io/api/#live-reload&quot;&gt;esbuild documentation on live reloading&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new EventSource(&quot;/esbuild&quot;).addEventListener(&quot;change&quot;, () =&amp;gt; location.reload());
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Implementation&lt;/h3&gt;
&lt;p&gt;And now the big reveal: we can get the whole development environment with one dependency,&lt;sup&gt;&lt;a href=&quot;#fn5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; two shell commands and zero config files!&lt;/p&gt;
&lt;p&gt;Here’s the command for the dev server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;esbuild src/index.html src/index.tsx \
  --loader:.html=copy \
  --outdir=build --bundle --watch \
  --servedir=build --serve-fallback=src/index.html \
  --inject:src/livereload.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This builds atop my &lt;a href=&quot;https://github.com/jakelazaroff/til/blob/main/esbuild/run-a-development-server-with-live-reload.md&quot;&gt;TIL on using esbuild to run a dev server with live reload&lt;/a&gt;. Let’s go through what each line does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;esbuild src/index.html src/index.tsx&lt;/code&gt; runs esbuild with &lt;code&gt;src/index.html&lt;/code&gt; and &lt;code&gt;src/index.tsx&lt;/code&gt; as entrypoints. We don’t need to specify any CSS files because esbuild will gather them as they’re imported into JS files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--loader:.html=copy‌&lt;/code&gt; tells esbuild to &lt;a href=&quot;https://esbuild.github.io/content-types/#copy&quot;&gt;copy&lt;/a&gt; files ending in &lt;code&gt;.html&lt;/code&gt; unchanged to the &lt;code&gt;build&lt;/code&gt; folder. This also gets triggered when the HTML file changes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--outdir=build --bundle&lt;/code&gt; tells esbuild to bundle all the files and place them in the &lt;code&gt;build&lt;/code&gt; folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember those flags, because we’ll use them both when developing locally and building for production. The rest of the flags are specific to development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--watch&lt;/code&gt; tells esbuild to rebuild when any source files change.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--servedir=build&lt;/code&gt; serves all static files from the &lt;code&gt;build&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--serve-fallback=src/index.html&lt;/code&gt; serves &lt;code&gt;src/index.html&lt;/code&gt; instead of a 404 page (useful for client-side routing)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--inject:src/livereload.js&lt;/code&gt; appends &lt;code&gt;src/livereload.js&lt;/code&gt; to the end of the JS bundle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s how to build for production:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;esbuild src/index.html src/index.tsx \
  --loader:.html=copy \
  --outdir=build --bundle --minify
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s mostly the same! There are only two differences: the last two lines with the development server flags are gone, and &lt;code&gt;--watch&lt;/code&gt; has been replaced with &lt;code&gt;--minify&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If these commands seem kinda gnarly, remember that we don’t have to type out them every single time. They can go in the &lt;code&gt;scripts&lt;/code&gt; section of our &lt;code&gt;package.json&lt;/code&gt; file. I’ve consolidated this one a bit by moving the common flags into an &lt;code&gt;esbuild&lt;/code&gt; script, and having &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; scripts add the extra flags at the end.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;esbuild&quot;: &quot;esbuild src/index.html src/index.tsx --loader:.html=copy --outdir=build --bundle&quot;,
    &quot;start&quot;: &quot;npm run esbuild -- --watch --servedir=build --serve-fallback=src/index.html --inject:src/livereload.js&quot;,
    &quot;build&quot;: &quot;npm run esbuild -- --minify&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Extra Credit&lt;/h3&gt;
&lt;p&gt;Okay, so we’ve covered our goals. But esbuild can do a lot more. Here are some other flags you might think of enabling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--sourcemap&lt;/code&gt; makes debugging easier by exporting &lt;code&gt;.js.map&lt;/code&gt; and &lt;code&gt;.css.map&lt;/code&gt; files that tell the browser how your bundled code relates to your source code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--loader&lt;/code&gt; lets you import different file types from JS and CSS. A particularly useful option is the &lt;code&gt;file&lt;/code&gt; loader, which copies the file to the build directory and embeds the file name as a string. For example, to be able to use &lt;code&gt;url()&lt;/code&gt; to load &lt;code&gt;.woff2&lt;/code&gt; files from a stylesheet, you’d add &lt;code&gt;--loader:.woff2=file&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--jsx-factory&lt;/code&gt; lets you change how JSX is compiled into function calls if you’d rather replace React with a different library like Preact.&lt;/li&gt;
&lt;li&gt;This isn’t a flag, but in your &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/tsconfig-json.html&quot;&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/a&gt; (or &lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig&quot;&gt;&lt;code&gt;jsconfig.json&lt;/code&gt;&lt;/a&gt; if you don’t use TypeScript) you can set &lt;code&gt;paths&lt;/code&gt; to &lt;code&gt;{ &quot;~/*&quot;: [&quot;./src/*&quot;] }&lt;/code&gt; in order to use &lt;code&gt;~&lt;/code&gt; to reference your files relative to the source root. For example, you’d import &lt;code&gt;src/components/App.tsx&lt;/code&gt; with &lt;code&gt;import App from &quot;~/components/App&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&apos;s it! Happy hacking! If you come up with any cool additions to this, &lt;a href=&quot;https://mastodon.social/@jakelazaroff&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Updates&lt;/h3&gt;
&lt;p&gt;Originally, this post recommended &lt;code&gt;--loader:.module.css=local-css&lt;/code&gt; in order to use the &lt;code&gt;local-css&lt;/code&gt; loader for &lt;code&gt;.module.css&lt;/code&gt; files. As of &lt;a href=&quot;https://github.com/evanw/esbuild/releases/tag/v0.19.0&quot;&gt;esbuild v0.19.0&lt;/a&gt;, that’s the default behavior, so that flag is no longer necessary!&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The W3C has started chipping away at some of these. The new &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog&quot;&gt;&lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element&lt;/a&gt; is a native way to build accessible modals, &lt;a href=&quot;https://developer.chrome.com/blog/tether-elements-to-each-other-with-css-anchor-positioning/&quot;&gt;CSS anchor positioning&lt;/a&gt; looks to make things like popovers significantly easier and the &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/view-transitions/&quot;&gt;View Transition API&lt;/a&gt; will let us animate page navigation with just HTML and CSS. Among others! &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technically, NPM lists &lt;a href=&quot;https://www.npmjs.com/package/esbuild?activeTab=dependencies&quot;&gt;22 dependencies&lt;/a&gt; of esbuild, which is misleading — they’re all first-party shims for esbuild’s own native binaries, so from a JavaScript perspective you could say esbuild has zero! But esbuild is written in Go and although NPM can’t inspect it, if you look in the &lt;code&gt;go.mod&lt;/code&gt; file you can see that the entire project only uses &lt;a href=&quot;https://github.com/evanw/esbuild/blob/main/go.mod&quot;&gt;a single third-party dependency&lt;/a&gt;. &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It was actually possible to get CSS modules before with &lt;a href=&quot;https://esbuild.github.io/plugins/&quot;&gt;esbuild plugins&lt;/a&gt;, such as &lt;a href=&quot;https://github.com/mhsdesign/esbuild-plugin-lightningcss-modules&quot;&gt;this one&lt;/a&gt; that uses Devon Govett’s excellent &lt;a href=&quot;https://lightningcss.dev&quot;&gt;Lightning CSS&lt;/a&gt;. But every extra dependency and config file adds friction, which is why I try to minimize them when building something simple. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This example assumes we’re using TypeScript. If you don’t want to use TypeScript, just change the &lt;code&gt;.tsx&lt;/code&gt; extensions to &lt;code&gt;.jsx&lt;/code&gt; and delete that &lt;code&gt;types.d.ts&lt;/code&gt; file. &lt;a href=&quot;#fnref4&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It probably goes without saying, but that one dependency is esbuild. &lt;a href=&quot;#fnref5&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>Were React Hooks a Mistake?</title><link>https://jakelazaroff.com/words/were-react-hooks-a-mistake/</link><guid isPermaLink="true">https://jakelazaroff.com/words/were-react-hooks-a-mistake/</guid><description>People like signals because signal-based components are far more similar to class components than to function components with hooks.</description><pubDate>Sun, 05 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The web dev community has spent the past few weeks buzzing about signals, a reactive programming pattern that enables very efficient UI updates. Devon Govett wrote &lt;a href=&quot;https://twitter.com/devongovett/status/1629540226589663233&quot;&gt;a thought-provoking Twitter thread about signals and mutable state&lt;/a&gt;. Ryan Carniato responded with &lt;a href=&quot;https://dev.to/this-is-learning/react-vs-signals-10-years-later-3k71&quot;&gt;an excellent article comparing signals with React&lt;/a&gt;. Good arguments on all sides; the discussion has been really interesting to watch.&lt;/p&gt;
&lt;p&gt;One thing the discourse makes clear is that there are a lot of people for whom the React programming model just does not click. Why is that?&lt;/p&gt;
&lt;p&gt;I think the issue is that people&apos;s mental model of components doesn’t match how function components with hooks work in React. I’m going to make an audacious claim: people like signals because &lt;strong&gt;signal-based components are far more similar to class components than to function components with hooks&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Let’s rewind a bit. React components used to look like this:&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Game extends React.Component {
  state = { count: 0, started: false };

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  start() {
    if (!this.state.started) setTimeout(() =&amp;gt; alert(`Your score was ${this.state.count}!`), 5000);
    this.setState({ started: true });
  }

  render() {
    return (
      &amp;lt;button
        onClick={() =&amp;gt; {
          this.increment();
          this.start();
        }}
      &amp;gt;
        {this.state.started ? &quot;Current score: &quot; + this.state.count : &quot;Start&quot;}
      &amp;lt;/button&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each component was an instance of the class &lt;code&gt;React.Component&lt;/code&gt;. State was kept in the property &lt;code&gt;state&lt;/code&gt;, and callbacks were just methods on the instance. When React needed to render a component, it would call the &lt;code&gt;render&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;You can still write components like this. The syntax hasn’t been removed. But back in 2015, React introduced something new: &lt;a href=&quot;https://reactjs.org/blog/2015/10/07/react-v0.14.html&quot;&gt;stateless function components&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function CounterButton({ started, count, onClick }) {
  return &amp;lt;button onClick={onClick}&amp;gt;{started ? &quot;Current score: &quot; + count : &quot;Start&quot;}&amp;lt;/button&amp;gt;;
}

class Game extends React.Component {
  state = { count: 0, started: false };

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  start() {
    if (!this.state.started) setTimeout(() =&amp;gt; alert(`Your score was ${this.state.count}!`), 5000);
    this.setState({ started: true });
  }

  render() {
    return (
      &amp;lt;CounterButton
        started={this.state.started}
        count={this.state.count}
        onClick={() =&amp;gt; {
          this.increment();
          this.start();
        }}
      /&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the time, there was no way to add state to these components — it had to be kept in class components and passed in as props. The idea was that most of your components would be stateless, powered by a few stateful components near the top of the tree.&lt;/p&gt;
&lt;p&gt;When it came to writing the class components, though, things were… awkward. Composition of stateful logic was particularly tricky. Say you needed multiple different classes to listen for window resize events. How would you do that without rewriting the same instance methods in each one? What if you needed them to interact with the component state? React tried to solve this problem with &lt;a href=&quot;https://reactjs.org/docs/react-without-es6.html#mixins&quot;&gt;mixins&lt;/a&gt;, but they were &lt;a href=&quot;https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html&quot;&gt;quickly deprecated&lt;/a&gt; once the team realized the drawbacks.&lt;/p&gt;
&lt;p&gt;Also, people really liked function components! There were even &lt;a href=&quot;https://github.com/acdlite/recompose&quot;&gt;libraries for adding state to them&lt;/a&gt;. So perhaps it’s not surprising that React came up with a built-in solution: hooks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Game() {
  const [count, setCount] = useState(0);
  const [started, setStarted] = useState(false);

  function increment() {
    setCount(count + 1);
  }

  function start() {
    if (!started) setTimeout(() =&amp;gt; alert(`Your score was ${count}!`), 5000);
    setStarted(true);
  }

  return (
    &amp;lt;button
      onClick={() =&amp;gt; {
        increment();
        start();
      }}
    &amp;gt;
      {started ? &quot;Current score: &quot; + count : &quot;Start&quot;}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I first tried them out, hooks were a revelation. They really did make it easy to encapsulate behavior and reuse stateful logic. I jumped in headfirst; the only class components I’ve written since then have been error boundaries.&lt;/p&gt;
&lt;p&gt;That said — although at first glance this component works the same as the class component above, there’s an important difference. Maybe you&apos;ve spotted it already: your score in the UI will be updated, but when the alert shows up it’ll always show you 0. Because &lt;code&gt;setTimeout&lt;/code&gt; only happens in the first call to &lt;code&gt;start&lt;/code&gt;, it closes over the initial &lt;code&gt;count&lt;/code&gt; value and that’s all it’ll ever see.&lt;/p&gt;
&lt;p&gt;You might think you could fix this with &lt;code&gt;useEffect&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Game() {
  const [count, setCount] = useState(0);
  const [started, setStarted] = useState(false);

  function increment() {
    setCount(count + 1);
  }

  function start() {
    setStarted(true);
  }

  useEffect(() =&amp;gt; {
    if (started) {
      const timeout = setTimeout(() =&amp;gt; alert(`Your score is ${count}!`), 5000);
      return () =&amp;gt; clearTimeout(timeout);
    }
  }, [count, started]);

  return (
    &amp;lt;button
      onClick={() =&amp;gt; {
        increment();
        start();
      }}
    &amp;gt;
      {started ? &quot;Current score: &quot; + count : &quot;Start&quot;}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This alert will show the correct count. But there’s a new issue: if you keep clicking, the game will never end! To prevent the effect function closure from getting “stale”, we add &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;started&lt;/code&gt; to the dependency array. Whenever they change, we get a new effect function that sees the updated values. But that new effect also sets a new timeout. Every time you click the button, you get a fresh five seconds before the alert shows up.&lt;/p&gt;
&lt;p&gt;In a class component, methods always have access to the most up-to-date state because they have a stable reference to the class instance. But in a function component, every render creates new callbacks that close over its own state. Each time the function is called, it gets its own closure. Future renders can’t change the state of past ones.&lt;/p&gt;
&lt;p&gt;Put another way: class components have a single instance &lt;strong&gt;per mounted component&lt;/strong&gt;, but function components have multiple “instances” — one &lt;strong&gt;per render&lt;/strong&gt;. Hooks just further entrench that constraint. It’s the source of all your problems with them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each render creates its own callbacks, which means anything that checks referential equality before running side effects — &lt;code&gt;useEffect&lt;/code&gt; and its siblings — will get triggered too often.&lt;/li&gt;
&lt;li&gt;Callbacks close over the state and props from their render, which means callbacks that persist between renders — because of &lt;code&gt;useCallback&lt;/code&gt;, asynchronous operations, timeouts, etc — will access stale data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;React gives you an escape hatch to deal with this: &lt;code&gt;useRef&lt;/code&gt;, a mutable object that keeps a stable identity between renders. I think of it as a way to teleport values back and forth between different instances of the same mounted component. With that in mind, here’s what a working version of our game might look like using hooks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Game() {
  const [count, setCount] = useState(0);
  const [started, setStarted] = useState(false);
  const countRef = useRef(count);

  function increment() {
    setCount(count + 1);
    countRef.current = count + 1;
  }

  function start() {
    if (!started) setTimeout(() =&amp;gt; alert(`Your score was ${countRef.current}!`), 5000);
    setStarted(true);
  }

  return (
    &amp;lt;button
      onClick={() =&amp;gt; {
        increment();
        start();
      }}
    &amp;gt;
      {started ? &quot;Current score: &quot; + count : &quot;Start&quot;}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s pretty kludgy! We’re now tracking the count in two different places, and our &lt;code&gt;increment&lt;/code&gt; function has to update them both. The reason that it works is that every &lt;code&gt;start&lt;/code&gt; closure has access to the same &lt;code&gt;countRef&lt;/code&gt;; when we mutate it in one, all the others can see the mutated value as well. But we can’t get rid of &lt;code&gt;useState&lt;/code&gt; and only rely on &lt;code&gt;useRef&lt;/code&gt;, because changing a ref doesn’t cause React to re-render. We’re stuck between two different worlds — the immutable state that we use to update the UI, and the mutable ref with the current state.&lt;/p&gt;
&lt;p&gt;Class components don’t have this drawback. The fact that each mounted component is an instance of the class gives us a kind of built-in ref. Hooks gave us a much better primitive for composing stateful logic, but it came at a cost.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Although they’re &lt;a href=&quot;https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob&quot;&gt;not new&lt;/a&gt;, signals have experienced a recent explosion in popularity, and seem to be used by most major frameworks other than React.&lt;/p&gt;
&lt;p&gt;The usual pitch is that they enable “fine-grained reactivity”. That means when state changes, they only update the specific pieces of the DOM that depend on it. For now, that usually ends up being faster than React, which recreates the full VDOM tree before discarding the parts that don’t change. But to me, these are all implementation details. People aren’t switching to these frameworks just for the performance. They’re switching because these frameworks offer a fundamentally different programming model.&lt;/p&gt;
&lt;p&gt;If we rewrite our little counter game with Solid, for example, we get something that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Game() {
  const [count, setCount] = createSignal(0);
  const [started, setStarted] = createSignal(false);

  function increment() {
    setCount(count() + 1);
  }

  function start() {
    if (!started()) setTimeout(() =&amp;gt; alert(`Your score was ${count()}!`), 5000);
    setStarted(true);
  }

  return (
    &amp;lt;button
      onClick={() =&amp;gt; {
        increment();
        start();
      }}
    &amp;gt;
      {started() ? &quot;Current score: &quot; + count() : &quot;Start&quot;}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks almost identical to the first hooks version! The only visible differences are that we’re calling &lt;code&gt;createSignal&lt;/code&gt; instead of &lt;code&gt;useState&lt;/code&gt;, and that &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;started&lt;/code&gt; are functions we call whenever we want to access the value. As with class and function components, though, the appearance of similarity belies an important difference.&lt;/p&gt;
&lt;p&gt;The key with Solid and other signal-based frameworks is that the component is only run once, and the framework sets up a data structure that automatically updates the DOM when signals change. Only running the component once means we only have one closure. Having only one closure gives us a stable instance per mounted component again, because closures are equivalent to classes.&lt;/p&gt;
&lt;p&gt;What?&lt;/p&gt;
&lt;p&gt;It’s true!&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; Fundamentally, they’re both just bundles of data and behavior. Closures are primarily behavior (the function call) with associated data (closed over variables), while classes are primarily data (the instance properties) with associated behavior (methods). If you really wanted to, you could write either one in terms of the other.&lt;/p&gt;
&lt;p&gt;Think about it. With class components…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The constructor sets up everything the component needs to render (setting initial state, binding instance methods, etc).&lt;/li&gt;
&lt;li&gt;When you update the state, React mutates the class instance, calls the &lt;code&gt;render&lt;/code&gt; method and makes any necessary changes to the DOM.&lt;/li&gt;
&lt;li&gt;All functions have access to up-to-date state stored on the class instance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whereas with signals components…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The function body sets up everything the component needs to render (setting up the data flow, creating DOM nodes, etc).&lt;/li&gt;
&lt;li&gt;When you update a signal, the framework mutates the stored value, runs any dependent signals and makes any necessary changes to the DOM.&lt;/li&gt;
&lt;li&gt;All functions have access to up-to-date state stored in the function closure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From this point of view, it’s a little easier to see the tradeoffs. Like classes, signals are &lt;strong&gt;mutable&lt;/strong&gt;. That might seem a little weird. After all, the Solid component isn’t assigning anything — it’s calling &lt;code&gt;setCount&lt;/code&gt;, just like React! But remember that &lt;code&gt;count&lt;/code&gt; isn’t a value itself — it’s a function that returns the current state of the signal. When &lt;code&gt;setCount&lt;/code&gt; is called, it mutates the signal, and further calls to &lt;code&gt;count()&lt;/code&gt; will return the new value.&lt;/p&gt;
&lt;p&gt;Although Solid&apos;s &lt;code&gt;createSignal&lt;/code&gt; looks like React&apos;s &lt;code&gt;useState&lt;/code&gt;, signals are really more like refs: stable references to mutable objects. The difference is that in React, which is built around immutability, refs are an escape hatch that has no effect on rendering. But frameworks like Solid put signals front and center. Rather than ignoring them, the framework reacts when they change, updating only the specific parts of the DOM that use their values.&lt;/p&gt;
&lt;p&gt;The big consequence of this is that the UI is no longer a pure function of state. That&apos;s why React embraces immutability: it guarantees that the state and UI are consistent. When mutations are introduced, you also need a way to keep the UI in sync. Signals promise to be a reliable way to do that, and their success will hinge on their ability to deliver on that promise.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;To recap:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First we had class components, which kept state in a single instance shared between renders.&lt;/li&gt;
&lt;li&gt;Then we had function components with hooks, in which each render had its own isolated instance and state.&lt;/li&gt;
&lt;li&gt;Now we’re swinging toward signals, which keep state in a single instance again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So were React hooks a mistake? They definitely made it easier to break up components and reuse stateful logic.&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; Even as I type this, if you were to ask me whether I’ll be abandoning hooks and returning to class components, I’d tell you no.&lt;/p&gt;
&lt;p&gt;At the same time, it’s not lost on me that the appeal of signals is regaining what we already had with class components. React made a big bet on immutability, but people have been looking for ways to have their cake and eat it too for a while now. That’s why libraries like &lt;a href=&quot;https://immerjs.github.io/immer/&quot;&gt;immer&lt;/a&gt; and &lt;a href=&quot;https://mobx.js.org&quot;&gt;MobX&lt;/a&gt; exist: it turns out that the ergonomics of working with mutable data can be really convenient.&lt;/p&gt;
&lt;p&gt;People seem to like the aesthetics of function components and hooks, though, and you can see their influence in newer frameworks. &lt;a href=&quot;https://www.solidjs.com/tutorial/introduction_effects&quot;&gt;Solid’s &lt;code&gt;createSignal&lt;/code&gt;&lt;/a&gt; is almost identical to React’s &lt;code&gt;useState&lt;/code&gt;. &lt;a href=&quot;https://preactjs.com/guide/v10/signals/#local-state-with-signals&quot;&gt;Preact’s &lt;code&gt;useSignal&lt;/code&gt;&lt;/a&gt; is similar as well. It’s hard to imagine that these APIs would look like they do without React having lead the way.&lt;/p&gt;
&lt;p&gt;Are signals better than hooks? I don’t think that’s the right question. Everything has tradeoffs, and we’re pretty sure about the tradeoffs signals make: they give up immutability and UI as a pure function of state, in exchange for better update performance and a stable, mutable instance per mounted component.&lt;/p&gt;
&lt;p&gt;Time will tell whether signals will bring back the problems React was created to fix. But right now, frameworks seem to be trying to strike a sweet spot between the composability of hooks and the stability of classes. At the very least, that’s an option worth exploring.&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Even before this, we had &lt;a href=&quot;https://web.archive.org/web/20160212215108/http://facebook.github.io/react/docs/displaying-data.html&quot;&gt;&lt;code&gt;React.createClass&lt;/code&gt;&lt;/a&gt;. We don’t speak of those days. &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technically, they’re equivalent to class &lt;strong&gt;instances&lt;/strong&gt; — AKA objects. Some people say &lt;a href=&quot;http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent&quot;&gt;“closures are a poor man’s objects”, and vice versa&lt;/a&gt;. &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Although I do wonder what React might have looked like if it leaned further into the class paradigm and implemented something like the &lt;a href=&quot;http://gameprogrammingpatterns.com/component.html&quot;&gt;component pattern in game development&lt;/a&gt;. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>Pathetic</title><link>https://jakelazaroff.com/words/pathetic/</link><guid isPermaLink="true">https://jakelazaroff.com/words/pathetic/</guid><description>The toxicity in the web development community needs to stop.</description><pubDate>Wed, 15 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There’s been a lot of drama in the web development community lately. Most of it is centered around JavaScript, and more specifically React. That’s not unusual, but what seems different right now is the amount of toxicity that’s been injected into the discourse.&lt;/p&gt;
&lt;p&gt;I don’t really want to name names, because I’m not trying to shame anyone in particular. But all the examples I’m about to bring up are from prominent voices in the community. You’ve seen their faces. You&apos;ve gotten their hot takes in your feeds.&lt;/p&gt;
&lt;p&gt;One developer wrote a reasonable blog post saying, essentially, that developers are trying to make the best tradeoffs they can, in response to another post sharply criticizing React. The author of the original post — another prominent developer — found the response and accused its author of “white knighting” for Facebook and Vercel.&lt;/p&gt;
&lt;p&gt;Another developer wrote a post about how most of the web actually runs on Wordpress and not the JavaScript frameworks that dominate the conversation. Fair enough! Except they also went out of their way to call the fact that only 4% of the top ten million sites use React &lt;strong&gt;pathetic&lt;/strong&gt; (emphasis theirs).&lt;/p&gt;
&lt;p&gt;When called out on using that word, they justified it by saying that React has been overhyped by unpleasant people. Earlier the same day, they&apos;d posted on Mastodon about a “weird Mormon dude” and his take on TypeScript. Who&apos;s supposed to be unpleasant here?&lt;/p&gt;
&lt;p&gt;A popular React influencer told someone they were trying to make themselves feel better about being left behind. It&apos;s like a crypto bro telling someone who won’t get on board to “have fun staying poor”. We don’t need to import that attitude into the web development community.&lt;/p&gt;
&lt;p&gt;Most alarmingly, someone said in a discussion that a member of the React team &lt;strong&gt;contacted their boss&lt;/strong&gt; over a critical tweet they made. I don’t even have words for how not okay that is. What the actual fuck.&lt;/p&gt;
&lt;p&gt;Can we just… not?&lt;/p&gt;
&lt;p&gt;Something is very wrong when your feelings about a JavaScript framework lead you to insult someone’s religion, or to contact their boss.&lt;/p&gt;
&lt;p&gt;Again, these aren’t random trolls on Twitter. They’re influential accounts who have hundreds of thousands of followers between them. They’re the “thought leaders” of our community. And right now, they&apos;re causing a ton of toxicity.&lt;/p&gt;
&lt;p&gt;You know what will hurt web development the most? What will discourage beginners and prevent us all from building better things? &lt;strong&gt;This right here.&lt;/strong&gt; No one wants to feel like they’re doing something morally wrong by choosing a framework, or like they’re being left behind if they don’t.&lt;/p&gt;
&lt;p&gt;I don’t really have a conclusion. Be good to each other, I suppose. We’re all just out here trying our best.&lt;/p&gt;
</content:encoded></item><item><title>In Defense of Cargo Culting</title><link>https://jakelazaroff.com/words/in-defense-of-cargo-culting/</link><guid isPermaLink="true">https://jakelazaroff.com/words/in-defense-of-cargo-culting/</guid><description>Cargo culting is a pejorative term that means someone is using a technology or following a pattern without understanding why. The discussion tends to myopically focus on how well a particular tool fits a problem&apos;s engineering constraints.</description><pubDate>Wed, 04 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In discussions on tech forums, you’ll sometimes hear people refer to &lt;a href=&quot;https://en.wikipedia.org/wiki/Cargo_cult&quot;&gt;cargo cults&lt;/a&gt;. The term was originally coined to describe indigenous tribes who came into contact with more technologically advanced colonizers (most famously, the US military in the South Pacific during World War II). After the colonizers left, the tribes would try to mimic their &quot;rituals&quot;, building airstrips and marching in formation in a futile attempt to attract more of the same supplies the colonizers summoned from the sky.&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;With regard to software, &lt;a href=&quot;https://en.wikipedia.org/wiki/Cargo_cult_programming&quot;&gt;cargo cult programming&lt;/a&gt; (or just &quot;cargo culting&quot;) is a similarly pejorative term that describes someone who uses a technology or follows a pattern without understanding why. Often, it&apos;s related to over-engineering: building software to handle a scale the developers anticipate, rather than the scale they&apos;re actually at. Another common accusation is that developers mindlessly mimic large tech companies, because if something works for Google or Facebook then it must work for them too.&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;There&apos;s some truth to all that, but the discussion tends to myopically focus on how well a particular tool fits a problem&apos;s engineering constraints.&lt;/p&gt;
&lt;h3&gt;Popularity&lt;/h3&gt;
&lt;p&gt;Cargo cults tend to form around popular or trendy tools. Developers see people around them using something and conclude that there must be a good reason, so they should use it as well. Or so the thinking goes.&lt;/p&gt;
&lt;p&gt;Some people choose based on the number of job opportunities a tool affords (“résumé-driven development”) and while that gets a bad rap, it’s a perfectly rational way to choose a stack for a personal project.&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; On the flip side, for companies hiring people to write code, the ability to find developers who are already familiar with their stack might be more important than picking the absolute best tools for the job.&lt;/p&gt;
&lt;p&gt;There are a lot of advantages to using popular tools! There’s probably documentation, tutorials and Stack Overflow answers readily available. Practices have been tried out and established. Plugins have been developed. Bugs have been found and fixed. If you&apos;re working on a team, it&apos;s much easier to arrive at a shared understanding of what&apos;s idiomatic, which patterns to use and what pitfalls to avoid. It can also help when onboarding new members, who can rely on an external community rather than the (lack of) documentation for a bespoke tool or niche framework.&lt;/p&gt;
&lt;p&gt;One important consideration here is whether something is is popular or merely trendy. A decent metric for this is longevity: &lt;a href=&quot;https://en.wikipedia.org/wiki/Lindy_effect&quot;&gt;if it’s been already around for a while, then it’ll probably stick around for a while longer&lt;/a&gt;. By picking a tool that a lot of people have used for a long time, you can rest assured that if you run into an issue, someone else will have run into it before you, and (hopefully) will have also written words on the internet about how to fix it.&lt;/p&gt;
&lt;h3&gt;Inexperience&lt;/h3&gt;
&lt;p&gt;Another reason people rely on popularity is inexperience: it’s not clear to them what the actual best solution is, so they just pick one and get to work.&lt;/p&gt;
&lt;p&gt;This is pretty common! Most of us have one or maybe two areas of deep expertise. But many projects we take on will involve new domains, or unexplored corners of familiar ones. Often, we specifically seek out such projects, since solving the puzzle sharpens our skills. This means we’re constantly being placed in situations where we have to make technology choices without fully understanding the constraints or the consequences.&lt;/p&gt;
&lt;p&gt;But before we develop that domain knowledge, we still have a job to do. At that stage, cargo culting is a reasonable way to make a decision. We don’t have the experience to make an educated choice on our own, so we turn to the wisdom of the crowd. A more charitable way to describe this sort of cargo culting is “&lt;a href=&quot;https://tomdale.net/2012/04/best-practices-exist-for-a-reason/&quot;&gt;following best practices&lt;/a&gt;”.&lt;/p&gt;
&lt;h3&gt;Knowledge Reuse&lt;/h3&gt;
&lt;p&gt;Conversely, people sometimes use tools they know well to solve problems for which they’re ill-suited.&lt;sup&gt;&lt;a href=&quot;#fn4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; This is the same sort of pragmatism that leads people to choose unfamiliar tools: they have a job to do, and getting it done quickly (or at all) is better than getting it done perfectly, eventually.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://mcfunley.com/choose-boring-technology&quot;&gt;Choose Boring Technology&lt;/a&gt;, Dan McKinley uses &quot;innovation tokens&quot; as a metaphor:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Let’s say every company gets about three innovation tokens. You can spend these however you want, but the supply is fixed for a long while. You might get a few more after you achieve a certain level of stability and maturity, but the general tendency is to overestimate the contents of your wallet. Clearly this model is approximate, but I think it helps.&lt;/p&gt;
&lt;p&gt;If you choose to write your website in NodeJS, you just spent one of your innovation tokens. If you choose to use MongoDB, you just spent one of your innovation tokens. If you choose to use service discovery tech that’s existed for a year or less, you just spent one of your innovation tokens. If you choose to write your own database, oh god, you’re in trouble.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The thing is, you don&apos;t just spend your tokens on technologies that are new and shiny. You pay a price whenever you use something with a lot of unknowns to you, personally. Sometimes, that&apos;s unavoidable — if you&apos;re building a web app, you probably need a database even if you don&apos;t know the first thing about SQL — but if you can avoid it, you should.&lt;/p&gt;
&lt;p&gt;In practice, this means &lt;a href=&quot;https://jakelazaroff.com/words/no-one-ever-got-fired-for-choosing-react/&quot;&gt;reducing risk by choosing tools you know well&lt;/a&gt;. This is why so many people tend to use the same stacks they use at work for their side projects: sure, it might be overkill, but they already know exactly how to use it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Ultimately, people choose technologies for a lot of reasons. From afar, it&apos;s easy to judge technology choices based on how well they fit the engineering problem. But &quot;the problem&quot; is usually multidimensional, and the engineering dimension not always the most important.&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I wish there were a less colonial name for this. Anthropologists now argue that &lt;a href=&quot;https://www.topic.com/who-is-john-frum&quot;&gt;the term and its connotation says more about Western ideology than the people it allegedly describes&lt;/a&gt;. &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some examples, like using Kubernetes for a blog or React for a marketing website, have basically become &lt;a href=&quot;https://twitter.com/dexhorthy/status/856639005462417409&quot;&gt;memes&lt;/a&gt;. &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Side projects should be entirely exempt from the cargo culting discussion. Why do you care about the stack of this app I made for fun? If you want me to build something in a specific way, hire me and pay me money. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technically, this isn’t cargo culting, but the resulting discourse tends to take the same shape so I’m including it here anyway. &lt;a href=&quot;#fnref4&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>Integrating My Blog with Mastodon</title><link>https://jakelazaroff.com/words/integrating-my-blog-with-mastodon/</link><guid isPermaLink="true">https://jakelazaroff.com/words/integrating-my-blog-with-mastodon/</guid><description>Turns out it’s pretty easy to set up an integration between your blog and the Fediverse!</description><pubDate>Fri, 30 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Decentralization is cool again! Twitter is burning and people are fleeing to greener pastures. There are a bunch of alternatives, but the most promising one is &lt;a href=&quot;https://joinmastodon.org/&quot;&gt;Mastodon&lt;/a&gt; — not because of any particular feature, but because it’s built on an open standard called &lt;a href=&quot;https://activitypub.rocks/&quot;&gt;ActivityPub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ActivityPub is a protocol: a bunch of conventions that apps can use to talk to each other. That means that if you build an app that understands ActivityPub, any other app that also understands it can communicate with it. Technically, this is called &lt;strong&gt;federation&lt;/strong&gt;, not decentralization, and the ecosystem of apps that work together this way is lovingly referred to as the Fediverse.&lt;/p&gt;
&lt;p&gt;I decided to dip my toes in and see how ActivityPub works. What I built is pretty simple: a tiny server that can interact with the rest of the Fediverse, plus a GitHub Actions workflow that posts whenever I publish a new blog post.&lt;/p&gt;
&lt;p&gt;The server I made is called &lt;a href=&quot;https://github.com/jakelazaroff/activitypub-starter-kit&quot;&gt;ActivityPub Starter Kit&lt;/a&gt;. It’s built with NodeJS and stores its data in SQLite. To make hacking on it easier, I also made &lt;a href=&quot;https://glitch.com/edit/#!/activitypub-starter-kit&quot;&gt;a simpler version on Glitch&lt;/a&gt;. As soon as you remix it, you get an account that you can follow from any Fediverse app — no extra setup required!&lt;/p&gt;
&lt;p&gt;In the rest of this blog post, I’ll show how easy it is to set up an integration between your blog and the Fediverse. We’ll assume that we’re using the Glitch app with an account called &lt;code&gt;jake&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So: ActivityPub Starter Kit. It’s a very basic ActivityPub server with only a few endpoints. There are two that matter to us:&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/jake/outbox&lt;/code&gt; is the &lt;a href=&quot;https://www.w3.org/TR/activitypub/#outbox&quot;&gt;Outbox&lt;/a&gt; for our account (if you’re following along at home, replace &lt;code&gt;jake&lt;/code&gt; with whatever account name you choose). The response contains a list of posts in reverse chronological order. We’ll use this to check which blog posts have already been posted to the Fediverse.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/admin/create&lt;/code&gt; adds a new post to the Outbox and notifies all our followers that we’ve.&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My blog is built on a static site generator that outputs an RSS feed. So there are four basic steps here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the latest post from the RSS feed.&lt;/li&gt;
&lt;li&gt;Get the latest post from the ActivityPub Outbox.&lt;/li&gt;
&lt;li&gt;Compare the URLs.&lt;/li&gt;
&lt;li&gt;If they don’t match, create a new ActivityPub post and notify followers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are only two libraries we need for this: &lt;a href=&quot;https://www.npmjs.com/package/node-fetch&quot;&gt;node-fetch&lt;/a&gt; (to make HTTP requests more easily) and &lt;a href=&quot;https://www.npmjs.com/package/fast-xml-parser&quot;&gt;fast-xml-parser&lt;/a&gt; (to parse the RSS feed into JSON).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { XMLParser } from &quot;fast-xml-parser&quot;;
import fetch from &quot;node-fetch&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few variables we’ll need that we may as well define now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const RSS_URL = &quot;https://jakelazaroff.com/rss.xml&quot;;
const ACTIVITYPUB_HOST = &quot;https://shine-thunder-forgery.glitch.me&quot;;
const OUTBOX_URL = ACTIVITYPUB_HOST + &quot;/jake/outbox&quot;;
const CREATE_URL = ACTIVITYPUB_HOST + &quot;/admin/create&quot;;
const ADMIN_USERNAME = &quot;adminuser&quot;;
const ADMIN_PASSWORD = &quot;secretpassword!&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RSS_URL&lt;/code&gt; is the URL of the RSS feed with the blog posts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ACTIVITYPUB_HOST&lt;/code&gt; is the URL of the ActivityPub server (just the protocol and hostname, so we can build our other URLs).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OUTBOX_URL&lt;/code&gt; is the URL of the Outbox.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CREATE_URL&lt;/code&gt; is the URL of the endpoint we’ll use to actually create the post.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ADMIN_USERNAME&lt;/code&gt; and &lt;code&gt;ADMIN_PASSWORD&lt;/code&gt; correspond to environment variables we can set in the server to prevent randos from creating posts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, onto the main event. First, we’ll fetch the RSS feed and get the latest blog post:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(`Fetching RSS feed from ${RSS_URL}…`);
const rss = await fetch(RSS_URL);
const feed = new XMLParser().parse(await rss.text());
const latest = feed.rss.channel.item[0];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we’ll fetch the ActivityPub feed and get the latest post there:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(`Fetching ActivityPub feed from ${OUTBOX_URL}…`);
const outbox = await fetch(OUTBOX_URL);
const posts = await outbox.json();
const post = posts.orderedItems[0];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the latest post from each, we can check to see if their URLs match:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (latest.link === post.object?.url) console.log(&quot;No new post in feed.&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If they don’t match, we’ll make a new post in the ActivityPub feed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;else {
  console.log(&quot;Posting to ActivityPub feed…&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we can send the request, we need to calculate the Basic authentication header. That’s just the string &lt;code&gt;Basic username:password&lt;/code&gt;, encoded in base 64:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const authorization = &quot;Basic &quot; + Buffer.from(ADMIN_USERNAME + &quot;:&quot; + ADMIN_PASSWORD).toString(&quot;base64&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to create the request body. ActivityPub supports &lt;a href=&quot;https://www.w3.org/TR/activitystreams-vocabulary/#activity-types&quot;&gt;a bunch of different types of &quot;Objects&quot;&lt;/a&gt;, but since this is a blog, the posts will all be of type &lt;a href=&quot;https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article&quot;&gt;Article&lt;/a&gt;. We’ll supply &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;published&lt;/code&gt; with data from the RSS feed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const date = new Date(latest.pubDate);
const body = JSON.stringify({
  object: {
    type: &quot;Article&quot;,
    url: latest.link,
    title: latest.title,
    summary: latest.description,
    content: latest[&quot;content:encoded&quot;],
    published: date.toISOString()
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll also notice that we’re nesting everything inside an &lt;code&gt;object&lt;/code&gt; property. That’s because each item in the Outbox is actually a &lt;a href=&quot;https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create&quot;&gt;Create Activity&lt;/a&gt; that &lt;strong&gt;contains&lt;/strong&gt; an Object.&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; That Activity has its own set of properties, and we can override them by including them in the request body.&lt;/p&gt;
&lt;p&gt;For example, if we wanted to set the ActivityPub post’s date to be the same as the blog post, we could add &lt;code&gt;published&lt;/code&gt; to the top level alongside &lt;code&gt;object&lt;/code&gt;, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const date = new Date(latest.pubDate);
const body = JSON.stringify({
  published: date.toISOString(),
  object: {
    type: &quot;Article&quot;,
    url: latest.link,
    title: latest.title,
    summary: latest.description,
    content: latest[&quot;content:encoded&quot;],
    published: date.toISOString()
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After we have the body, all that’s left is to send off the request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const res = await fetch(CREATE_URL, {
    method: &quot;POST&quot;,
    body,
    headers: { authorization, &quot;content-type&quot;: &quot;application/json&quot; },
  });

  if (!res.ok) throw new Error(`Got ${res.status} ${res.statusText} when creating post.`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s it! People can now follow your blog in the Fediverse.&lt;/p&gt;
&lt;p&gt;Here’s &lt;a href=&quot;https://gist.github.com/jakelazaroff/5256e91395188da72cecbf086723ee6f&quot;&gt;the whole script&lt;/a&gt;. It’s short — barely 40 lines. I included my full GitHub Actions deploy workflow in there, in case the context helps.&lt;/p&gt;
&lt;p&gt;And of course, this is just one example of what to build. You could have your ActivityPub server follow people and turn their posts into a reading list or a &lt;a href=&quot;https://indieweb.org/blogroll&quot;&gt;blogroll&lt;/a&gt;. You could make a bot that looks up information, or uses ChatGPT to write poems for people. Check out &lt;a href=&quot;https://github.com/jakelazaroff/activitypub-starter-kit&quot;&gt;ActivityPub Starter Kit&lt;/a&gt; and make something cool or quirky or fun!&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Technically, there’s also a third: &lt;code&gt;/jake/inbox&lt;/code&gt;, our account’s &lt;a href=&quot;https://www.w3.org/TR/activitypub/#inbox&quot;&gt;Inbox&lt;/a&gt;. We don’t ever make requests to our own Inbox, though — other servers will do that when they want to notify us of something. When we make a new post, ActivityPub Starter Kit will go through each follower and make a request to &lt;strong&gt;their&lt;/strong&gt; Inbox. &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/admin/create&lt;/code&gt; is not, strictly speaking, an ActivityPub endpoint, but it does follow the client-to-server interaction guidance on &lt;a href=&quot;https://www.w3.org/TR/activitypub/#object-without-create&quot;&gt;Object creation without the Create Activity&lt;/a&gt;. &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In ActivityPub parlance, an &lt;strong&gt;Actor&lt;/strong&gt; (the account) performs an &lt;strong&gt;Activity&lt;/strong&gt; (some sort of action) on an &lt;strong&gt;Object&lt;/strong&gt; (a thing). Darius Kazemi has &lt;a href=&quot;https://tinysubversions.com/notes/reading-activitypub/&quot;&gt;a good blog post breaking this down a bit more&lt;/a&gt;. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>A React Developer’s First Take on Solid</title><link>https://jakelazaroff.com/words/a-react-developers-first-take-on-solid/</link><guid isPermaLink="true">https://jakelazaroff.com/words/a-react-developers-first-take-on-solid/</guid><description>Although not quite there yet in terms of community, Solid and Solid Start are looking real promising.</description><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I start a new project, I tend to rely on tools I already know well. I have a vision in my head, and learning a new technology as I try to build something is a recipe for getting nothing done. Usually, for projects on the web, &lt;a href=&quot;https://jake.nyc/words/no-one-ever-got-fired-for-choosing-react/&quot;&gt;that means picking React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That said, I do keep an eye on the JavaScript ecosystem as a whole, and one of the frameworks that has piqued my interest for a while is &lt;a href=&quot;https://www.solidjs.com/&quot;&gt;Solid&lt;/a&gt;. It’s a reactive JavaScript frameworks, which basically means it models your app as streams of data and automatically updates the UI when a dependency changes. That might sound abstract and weird, but we’ll unpack it in a bit.&lt;/p&gt;
&lt;p&gt;I recently started a project involving server-side rendering. While looking into technologies to use,&lt;sup&gt;&lt;a href=&quot;#fn1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; I discovered that &lt;a href=&quot;https://www.solidjs.com/blog/introducing-solidstart&quot;&gt;the Solid team had recently released Solid Start&lt;/a&gt; — a Next.js-esque “meta-framework” that makes the experience of working with Solid easier and more seamless.&lt;/p&gt;
&lt;p&gt;I haven’t gotten a chance to work with it in depth yet, but I have used a decent amount of what it has to offer. So far, I’m really enjoying it. It’s similar enough to React that there’s not a steep learning curve, and some of the features seem genuinely transformative.&lt;/p&gt;
&lt;p&gt;There are two parts to this post. First, my impression of the Solid framework itself; and second, my impression of Solid Start.&lt;/p&gt;
&lt;p&gt;So, without further ado: a React developer’s first take on Solid.&lt;/p&gt;
&lt;h3&gt;The Solid Framework&lt;/h3&gt;
&lt;p&gt;The creator of Solid, Ryan Carniato, once quoted someone saying &lt;a href=&quot;https://twitter.com/ryancarniato/status/1318093967578206210&quot;&gt;“Svelte is to Vue as Solid is to React”&lt;/a&gt;. That rings true to me. It’s not “just JavaScript” in the same way React is — if you look at the compiled code, it’s definitely doing more than just de-sugaring JSX into function calls. But the code you’re &lt;strong&gt;writing&lt;/strong&gt; is JavaScript, not a language that’s bespoke to the framework.&lt;/p&gt;
&lt;p&gt;Here’s what a simple component might look like in Solid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { render } from &quot;solid-js/web&quot;;
import { createSignal } from &quot;solid-js&quot;;

function Greeter() {
  const [name, setName] = createSignal(&quot;Jake&quot;);

  const yelling = () =&amp;gt; name().toUpperCase();

  return (
    &amp;lt;&amp;gt;
      &amp;lt;p class=&quot;greeting&quot;&amp;gt;Hello, {yelling()}!&amp;lt;/p&amp;gt;
      &amp;lt;input
        value={name()}
        onChange={event =&amp;gt; {
          setName(event.currentTarget.value);
        }}
      /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

render(() =&amp;gt; &amp;lt;Greeter /&amp;gt;, document.getElementById(&quot;app&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could be forgiven for thinking you’re looking at a React component. From a syntax point of view, the only difference at this point is the use of &lt;code&gt;class&lt;/code&gt; instead of &lt;code&gt;className&lt;/code&gt;.&lt;sup&gt;&lt;a href=&quot;#fn2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Under the hood, however, they’re very different. In React, a component function gets called every render, and React modifies the DOM based on the difference between the return values. In Solid, a component function gets called &lt;strong&gt;once&lt;/strong&gt; over the entire lifetime of the component, and Solid only updates the DOM for each property that changes.&lt;/p&gt;
&lt;p&gt;Let’s break apart the example. There are four relevant lines:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;const [name, setName] = createSignal(&quot;Jake&quot;);&lt;/code&gt; looks like React’s &lt;code&gt;useState&lt;/code&gt;, but with one key difference: &lt;code&gt;name&lt;/code&gt; is not the value itself, but a function that returns the current value. So in this case, you could call &lt;code&gt;name()&lt;/code&gt; and it would return &quot;Jake&quot;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const yelling = () =&amp;gt; name().toUpperCase();&lt;/code&gt; is where it becomes important that &lt;code&gt;name&lt;/code&gt; is a function, rather than a primitive value. Any time &lt;code&gt;yelling&lt;/code&gt; is called, it gets the &lt;strong&gt;current&lt;/strong&gt; value, not just the value when the component was run. Under the hood, Solid remembers that the return value of &lt;code&gt;yelling&lt;/code&gt; depends on &lt;code&gt;name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;p class=&quot;greeting&quot;&amp;gt;Hello, {yelling()}!&amp;lt;/p&amp;gt;&lt;/code&gt; is the trickiest part, because JSX in Solid works slightly different than in React. Rather than just rendering this immediately, Solid remembers that the text of this &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; element depends on the result of the &lt;code&gt;yelling&lt;/code&gt; function. Then, when that function’s dependencies change, Solid figures out what actually changed and updates the DOM again.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setName(event.currentTarget.value);&lt;/code&gt; works roughly the same as in React. Calling it updates the value of the &lt;code&gt;name&lt;/code&gt; signal, which causes Solid to re-evaluate anything that depends on it — in this case, the &lt;code&gt;yelling&lt;/code&gt; function and the text inside the &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Essentially, what the component does is set up a pipeline from &lt;code&gt;createSignal&lt;/code&gt; to the DOM.&lt;sup&gt;&lt;a href=&quot;#fn3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; When you call &lt;code&gt;render&lt;/code&gt;, Solid remembers how that whole pipeline works. Then, when the value of the signal changes — in this case, by calling &lt;code&gt;setName&lt;/code&gt; — Solid runs &lt;strong&gt;only that pipeline&lt;/strong&gt;, not bothering with any other pipelines that it knows haven’t changed.&lt;/p&gt;
&lt;p&gt;I know that’s kinda hard to visualize. If you’re interested, Ryan has &lt;a href=&quot;https://youtu.be/O6xtMrDEhcE&quot;&gt;a good talk that goes in-depth into how this works&lt;/a&gt;. In practical terms, though, it doesn’t have a huge impact on the developer experience. There are a couple gotchas to keep in mind, but for the most part you can write your code as if you were building a React app and it just… works.&lt;/p&gt;
&lt;p&gt;Okay, so what are the gotchas? One, no destructuring props. Solid does some fancy &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot;&gt;Proxy&lt;/a&gt; stuff to track property access that gets messed up if you do.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// wrong
function Greeter({ name }) {
  return &amp;lt;p&amp;gt;Hello, {name}!&amp;lt;/p&amp;gt;;
}

// right
function Greeter(props) {
  return &amp;lt;p&amp;gt;Hello, {props.name}!&amp;lt;/p&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two, because component functions only run once, you need to wrap any derived values in their own functions. And three (or I guess two and a half) you use special components for looping and showing/hiding, rather than maps and ternaries.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// wrong
function TodoList(props) {
  const important = props.todos.map(todo =&amp;gt; todo.toUpperCase());

  return (
    &amp;lt;ul&amp;gt;
      {important.map(todo =&amp;gt; (
        &amp;lt;li&amp;gt;{todo}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

// right
function TodoList(props) {
  const important = () =&amp;gt; props.todos.map(todo =&amp;gt; todo.toUpperCase());

  return (
    &amp;lt;ul&amp;gt;
      &amp;lt;For each={important()}&amp;gt;{todo =&amp;gt; &amp;lt;li&amp;gt;{todo}&amp;lt;/li&amp;gt;}&amp;lt;/For&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, these are pretty small drawbacks. They’re kind of like React’s Rules of Hooks, in that you might initially think it’s weird to need to call functions in the same order each time until you realize that you already write most of your code like this anyway.&lt;/p&gt;
&lt;p&gt;Let’s look at one more example — the infamous counter app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Counter() {
  const [count, setCount] = createSignal(0);

  console.log(&quot;component&quot;, count());
  createEffect(() =&amp;gt; {
    console.log(&quot;effect&quot;, count());
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Count count={count()} /&amp;gt;
      &amp;lt;Button onIncrement={() =&amp;gt; setCount(count =&amp;gt; count + 1)} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function Count(props) {
  return &amp;lt;p&amp;gt;{props.count}&amp;lt;/p&amp;gt;;
}

function Button(props) {
  return (
    &amp;lt;button type=&quot;button&quot; onClick={props.onIncrement}&amp;gt;
      Increment
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, very similar to React. &lt;code&gt;createSignal&lt;/code&gt; plays the role of &lt;code&gt;useState&lt;/code&gt;, and the API is almost identical except that the &quot;state&quot; variable &lt;code&gt;count&lt;/code&gt; is a function that returns the current value. You’ll also notice I put a &lt;code&gt;console.log&lt;/code&gt; inside the &lt;code&gt;Counter&lt;/code&gt; component — once in the body itself, and once in &lt;code&gt;createEffect&lt;/code&gt;, which is Solid’s version of &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Solid knows that there’s a &quot;pipeline&quot; between that &lt;code&gt;count&lt;/code&gt; variable and the &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag, it and updates the DOM automatically when the value changes. It also knows that there’s another pipeline that from &lt;code&gt;count&lt;/code&gt; to &lt;code&gt;createEffect&lt;/code&gt;, so it reruns the effect.&lt;/p&gt;
&lt;p&gt;Here’s a link to &lt;a href=&quot;https://playground.solidjs.com/anonymous/c46db9de-2239-4eaa-b38f-efae3de38383&quot;&gt;a live version of this code&lt;/a&gt;, if it helps. If you run it and click the button a few times, you’ll see that the “component” log doesn’t happen when you click; these functions are, indeed, only called once. The &quot;effect&quot; log, on the other hand, happens every time the count is updated. You should see something like this in the console:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;component 0
effect 0
effect 1
effect 2
effect 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All this is nice, but what’s the advantage over React? For one, &lt;a href=&quot;https://bundlephobia.com/package/solid-js&quot;&gt;it’s small&lt;/a&gt;, although that makes less of a difference the larger your app gets. There are marginal developer experience improvements, such as not having to track &lt;code&gt;createEffect&lt;/code&gt; dependencies. And finally: because it’s only updating the exact things that change, &lt;a href=&quot;https://krausest.github.io/js-framework-benchmark/current.html&quot;&gt;it’s also really fast&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Solid Start&lt;/h3&gt;
&lt;p&gt;If Solid is React, then Solid Start is Next.js. The Solid Start website cautions that it’s in beta, and they’re definitely not lying. But it also has some awesome features that seem genuinely transformative.&lt;/p&gt;
&lt;p&gt;First, it has the file system routing that’s become bog standard amongst frontend frameworks these days. The default styling solution is CSS modules. It supports both server-side rendering and client-side rendering. There are provided components for managing head tags.&lt;/p&gt;
&lt;p&gt;Let’s talk about loading data. Traditionally, single-page apps are split into two layers: a JavaScript frontend that runs in the browser, and a separate REST(ish) API that runs on the server, communicating with the client using JSON. Lately, frameworks like Next.js have begun combining the two, allowing developers to create “API routes” in the same codebase as their user-facing components.&lt;/p&gt;
&lt;p&gt;Solid Start’s answer to this is &lt;a href=&quot;https://start.solidjs.com/core-concepts/data-loading&quot;&gt;route data&lt;/a&gt; — a &lt;a href=&quot;https://remix.run/docs/en/v1/route/loader&quot;&gt;Remix-inspired feature&lt;/a&gt; that makes it super easy to load data, both on the server and client. If you export a function called &lt;code&gt;routeData&lt;/code&gt; from a page, calling the hook &lt;code&gt;useRouteData&lt;/code&gt; within the component will give you the data you returned from &lt;code&gt;routeData&lt;/code&gt;. The secret sauce is that Solid takes care of wiring up the HTTP request and getting the response back to your component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function routeData() {
  const [todos] = createResource(async () =&amp;gt; {
    // here’s where you’d load data if it weren’t hardcoded as an example
    return [&quot;brush teeth&quot;, &quot;get milk&quot;, &quot;publish blog post&quot;];
  });

  return { todos };
}

export default function Page() {
  const { todos } = useRouteData();

  return (
    &amp;lt;ul&amp;gt;
      &amp;lt;For each={todos()}&amp;gt;{todo =&amp;gt; &amp;lt;li&amp;gt;{todo.text}&amp;lt;/li&amp;gt;}&amp;lt;/For&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s for reading data. For writing, there are &lt;a href=&quot;https://start.solidjs.com/core-concepts/actions&quot;&gt;route actions&lt;/a&gt;. They’re pretty similar, except you call them from within your component code and wire them up to your JSX, like a hook. They even return a &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; component, which makes it super easy to create progressively enhanced HTML forms that work without JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function Page() {
  const [name, { Form }] = createRouteAction(async data =&amp;gt; {
    // here’s where you’d do some stuff with the data
    return data.get(&quot;name&quot;);
  });

  &amp;lt;&amp;gt;
    &amp;lt;Form&amp;gt;
      &amp;lt;label&amp;gt;
        Name
        &amp;lt;input name=&quot;name&quot; /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;button&amp;gt;Submit&amp;lt;/button&amp;gt;
    &amp;lt;/Form&amp;gt;
    &amp;lt;p&amp;gt;Hello, {name.result}!&amp;lt;/p&amp;gt;
  &amp;lt;/&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I won’t bury the lede: it’s pretty magical. Solid Start even includes functions &lt;code&gt;createServerData$&lt;/code&gt; and &lt;code&gt;createServerAction$&lt;/code&gt; for operations that should only happen on the server. You can make database queries in the same file as your components, and Solid will not only render the markup on the server when the page first loads, but also request new data and update the DOM whenever any of the parameters change. It’s way faster and less boilerplate-y than manually writing new REST endpoints or GraphQL resolvers.&lt;/p&gt;
&lt;p&gt;Frankly, I’m a little skeptical of how well this scales. As apps grow, they tend to require additional complexity. Things like authorization and rate limiting. Although the Solid Start documentation does include &lt;a href=&quot;https://start.solidjs.com/advanced/session&quot;&gt;an example of how to implement sessions&lt;/a&gt;, my gut says that bigger apps will still benefit from a separate “traditional” API. But for starting out quickly, or for apps that are likely to remain small, such as prototypes or side projects, this is perfect.&lt;/p&gt;
&lt;p&gt;Solid Start does offer the API routes found in Next.js as well, though. In files within the &lt;code&gt;routes&lt;/code&gt; directory, you can export functions named after HTTP verbs to respond to requests of that type. So, in case you do end up needing to scale your API beyond what route data and route actions can achieve, Solid Start gives you tools to migrate gradually.&lt;/p&gt;
&lt;p&gt;All that said, Solid Start is definitely beta software. Some of the rough edges I’m hoping get fixed before the 1.0 release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When rendering HTML on the server, Solid Start adds a bunch of weird &lt;code&gt;data-hk&lt;/code&gt; attributes to all the elements.&lt;/li&gt;
&lt;li&gt;Some naming conventions aren’t really clear, and the documentation doesn’t explain them. For example, some functions use &lt;code&gt;$&lt;/code&gt; as a suffix. My guess is that’s for functions that run on the server, but they all have &quot;server&quot; in the name anyway, so it seems redundant. Likewise, I’m not sure what the difference is between functions prefixed with &lt;code&gt;use&lt;/code&gt; and &lt;code&gt;create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createServerData$&lt;/code&gt; doesn’t accept a reactive function, and it’s not entirely clear why. Rather than automatically tracking dependencies such as query string parameters, you need to use them to construct a key which determines when to re-fetch the data. This overhead doesn’t exist on &quot;normal&quot; Solid functions like &lt;code&gt;createEffect&lt;/code&gt; and &lt;code&gt;createResource&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Possibly the most annoying thing is that data is inconsistently serialized when moving between the server and client. In my usage, it seems like objects in the initial page load are returned to the component as actual objects, but once you start taking actions on the page they become strings.&lt;sup&gt;&lt;a href=&quot;#fn4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; It’s a major leak in the client/server abstraction. I’m not sure whether it’s a bug or simply an oversight, but I really hope it gets fixed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Looking Forward&lt;/h3&gt;
&lt;p&gt;After all that, the question is: would I use Solid and/or Solid Start for a &quot;real&quot; project? I think the answer is &quot;not yet&quot;.&lt;/p&gt;
&lt;p&gt;I had a lot of fun building with it, and there are some really promising features. Despite having a lot of experience with React, building a full stack app with Solid took significantly less time and boilerplate. It’s a solid start.&lt;sup&gt;&lt;a href=&quot;#fn5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;But right now, &lt;a href=&quot;https://npmtrends.com/@angular/core-vs-react-vs-solid-js-vs-svelte-vs-vue&quot;&gt;React really is a behemoth&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;#fn6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt; when it comes to usage. Solid is definitely my favorite React alternative that I’ve tried, but that doesn’t mean the community agrees.&lt;/p&gt;
&lt;p&gt;Usage matters because the community is one of the most important metrics to me. Maybe the most important. That’s how a technology gets tested; bugs get found and fixed; plug-ins get created. It’s a bit of a chicken-and-egg situation, because the lack of those things is what prevents the community from forming in the first place.&lt;/p&gt;
&lt;p&gt;So: for large projects, I’m sticking to React. But for side projects, prototypes and experiments? I’m definitely adding Solid to my toolbox.&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I find it difficult to choose a stack when it comes to “traditional” web apps. To me, structuring my UI code as components makes sense even if it doesn’t run on the client. And I’d much rather write each component in the programming language itself, rather than an entirely different templating language. Finally — and I don’t know how this hasn’t caught on outside of single-page app frameworks — but CSS modules are a requirement (&lt;a href=&quot;https://jakelazaroff.com/words/tailwind-is-a-leaky-abstraction/&quot;&gt;I don’t like Tailwind&lt;/a&gt;). That’s pretty much the entire reason I don’t use &lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt;, even though it touts a “zero JavaScript by default” experience that checks all my other boxes: it doesn’t support CSS modules (&lt;a href=&quot;https://github.com/remix-run/remix/discussions/4631&quot;&gt;yet&lt;/a&gt;). &lt;a href=&quot;#fnref1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call it Stockholm Syndrome, but I actually prefer &lt;code&gt;className&lt;/code&gt; to &lt;code&gt;class&lt;/code&gt;. I know &lt;code&gt;class&lt;/code&gt; matches the HTML attribute, but &lt;code&gt;className&lt;/code&gt; matches the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/className&quot;&gt;JavaScript DOM property&lt;/a&gt;. Plus, &lt;code&gt;class&lt;/code&gt; is a reserved word, so you can’t destructure it out of the props object — although this isn’t a problem in Solid, because &lt;a href=&quot;https://www.solidjs.com/guides/faq#why-do-i-lose-reactivity-when-i-destructure-props&quot;&gt;you can’t destructure props at all&lt;/a&gt;. &lt;a href=&quot;#fnref2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In reactive programming lingo, you can think of a signal as a &lt;strong&gt;source&lt;/strong&gt;, and the DOM as a &lt;strong&gt;sink&lt;/strong&gt;. &lt;a href=&quot;#fnref3&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I ran into the serialization issue while trying to return &lt;code&gt;Date&lt;/code&gt; objects from &lt;code&gt;routeData&lt;/code&gt;. Eventually I gave up and just returned strings that I parsed in the component on the client side. &lt;a href=&quot;#fnref4&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I’m sorry! I held out as long as I could! &lt;a href=&quot;#fnref5&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are really weird spikes in the Svelte and Vue data that distort the graph. If you ignore those, you can see that React has over four times as many downloads as Vue and Angular, two orders of magnitude more than Svelte and &lt;strong&gt;three&lt;/strong&gt; orders of magnitude more than Solid. NPM downloads are only a directionally accurate indicator of popularity, but you don’t need a statistics degree to see that Solid has some catching up to do. &lt;a href=&quot;#fnref6&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded></item><item><title>Tailwind is a Leaky Abstraction</title><link>https://jakelazaroff.com/words/tailwind-is-a-leaky-abstraction/</link><guid isPermaLink="true">https://jakelazaroff.com/words/tailwind-is-a-leaky-abstraction/</guid><description>Although Tailwind does have some benefits, ultimately it’s just one more thing to learn.</description><pubDate>Tue, 29 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have to admit: as I’ve watched Tailwind enthusiastically adopted by more and more of the frontend community, I’ve remained skeptical. But, having never used it, I decided to keep quiet until I had an informed opinion.&lt;/p&gt;
&lt;p&gt;Well, I’ve spent the past few months at work learning Tailwind with an open mind. I can now confidently say that I do, in fact, dislike Tailwind, and I wouldn’t use it for any new projects.&lt;/p&gt;
&lt;p&gt;Tailwind is commonly described as “utility classes”, but that’s a bit of an understatement. It’s essentially a small language you write in the class attributes of your HTML that compiles to a combination of CSS rules and selectors — an &lt;strong&gt;abstraction&lt;/strong&gt; over CSS. But &lt;a href=&quot;https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/&quot;&gt;all abstractions leak&lt;/a&gt;, and Tailwind is very leaky.&lt;/p&gt;
&lt;p&gt;Let’s take transforms. I was trying to build an animation in which an element rotates in 3D space, so that it appears to come toward the viewer. To achieve that, I needed to rotate the element around the X axis. With CSS, this is easy: set a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/perspective&quot;&gt;&lt;code&gt;perspective&lt;/code&gt;&lt;/a&gt; on the parent element, and then set &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotateX&quot;&gt;&lt;code&gt;transform: rotateX&lt;/code&gt;&lt;/a&gt; on the element you want to animate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.parent {
  perspective: 1000px;
}

.animate {
  transform: rotateX(45deg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, Tailwind doesn’t support the &lt;code&gt;perspective&lt;/code&gt; property. Implementing any sort of 3D design requires breaking out of Tailwind.&lt;/p&gt;
&lt;p&gt;Not that it would have mattered, though. Tailwind only supports the unadorned &lt;code&gt;rotate&lt;/code&gt;, which uses the Z axis. If you want to rotate along any other axis, you’re out of luck. A discussion &lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss/discussions/3521&quot;&gt;has been ongoing for almost two years&lt;/a&gt;, with the official recommendation being to just write a custom JavaScript plugin to implement it.&lt;/p&gt;
&lt;p&gt;You might protest that this is a niche issue (that’s what the Tailwind team says in the discussion). But there are issues with utilities that you’re likely to encounter in regular usage, too.&lt;/p&gt;
&lt;p&gt;Even though it’s one of the main jobs of CSS, spacing things out has always been kind of a pain. Maybe that’s why Tailwind introduced the &lt;a href=&quot;https://tailwindcss.com/docs/space&quot;&gt;Space Between&lt;/a&gt; utilities. Just throw &lt;code&gt;space-x-2&lt;/code&gt; or some similar class on the parent, and the children will magically work themselves out!&lt;/p&gt;
&lt;p&gt;Of course, it’s not magic. What it actually does is use child and sibling selectors to set margins on the child elements. The aforelinked page shows the generated CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.space-x-2 &amp;gt; * + * {
  margin-left: 0.5rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That applies a &lt;code&gt;margin-left&lt;/code&gt; to every child after the first of an element with the class &lt;code&gt;space-x-2&lt;/code&gt;. The relevant consequence is that now, attempting to set a left margin using a selector with lower specificity — say, the selector generated by a Tailwind margin utility — will fail.&lt;/p&gt;
&lt;p&gt;I ran into this issue when I was doing exactly that: trying to use a margin utility on an element whose parent already had one of these space utilities applied to it. It took me a while to realize why every margin I set seemed to get ignored. These features are mutually exclusive.&lt;/p&gt;
&lt;p&gt;In the meantime, CSS has gotten much better at spacing! You can accomplish the exact same thing without Tailwind by using two properties:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;display: flex;
column-gap: 0.5rem;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Originally, I was going to write about how Tailwind doesn’t support attribute selectors. But a few days before I started this post, they came out with &lt;a href=&quot;https://tailwindcss.com/blog/tailwindcss-v3-2&quot;&gt;a huge update that added them&lt;/a&gt;. It’s a big improvement, but it also includes some of the leakiest parts of the abstraction yet. I’m referring in particular to the &lt;a href=&quot;https://tailwindcss.com/docs/hover-focus-and-other-states#using-arbitrary-variants&quot;&gt;“arbitrary variants” feature&lt;/a&gt; that allows you to include CSS selectors in your Tailwind classes.&lt;/p&gt;
&lt;p&gt;Let’s look at some of the selectors here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;[&amp;amp;_p]:mt-4&quot;&amp;gt;
  &amp;lt;!-- ... --&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;amp;&lt;/code&gt; sigil lets you control nesting, similar to Sass and (soon) &lt;a href=&quot;https://drafts.csswg.org/css-nesting/&quot;&gt;plain CSS&lt;/a&gt;. In CSS, the selector would be &lt;code&gt;&amp;amp; p&lt;/code&gt;. Tailwind requires underscores instead of spaces, though, because a space would split the class name in two.&lt;/p&gt;
&lt;p&gt;Here’s something a little more complex:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ul role=&quot;list&quot;&amp;gt;
  {#each items as item}
  &amp;lt;li class=&quot;lg:[&amp;amp;:nth-child(3)]:hover:underline&quot;&amp;gt;{item}&amp;lt;/li&amp;gt;
  {/each}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To understand this class, you need to know &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child&quot;&gt;how &lt;code&gt;nth-child&lt;/code&gt; works&lt;/a&gt;. That’s an abstraction leak. But worse than that, it highlights a key shortcoming of Tailwind. In CSS, to set multiple properties using the same selector, you can write it once and group all the properties together. In Tailwind, there’s no choice but to write the full query again for every property:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ul role=&quot;list&quot;&amp;gt;
  {#each items as item}
  &amp;lt;li
    class=&quot;lg:[&amp;amp;:nth-child(3)]:hover:underline lg:[&amp;amp;:nth-child(3)]:hover:font-bold lg:[&amp;amp;:nth-child(3)]:hover:text-blue-600 lg:[&amp;amp;:nth-child(3)]:hover:opacity-100&quot;
  &amp;gt;
    {item}
  &amp;lt;/li&amp;gt;
  {/each}
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the long horizontal scrollbar. This isn’t just an abstraction that leaks some of the underlying details. It’s an abstraction that actively makes the experience worse.&lt;/p&gt;
&lt;p&gt;Meanwhile, here’s the CSS that would be needed to accomplish the same thing. It’s still not simple. But it is scannable, and it doesn’t repeat the entire selector four different times.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@media (min-width: 1024px) {
  li:nth-child(3):hover {
    text-decoration: underline;
    font-weight: bold;
    color: var(--blue-600);
    opacity: 1;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I realize this is a bit of a catch-22. Before, I was criticizing Tailwind for hiding CSS internals; now I’m criticizing it for exposing them. But that’s kind of the point. Tailwind is a layer on top of CSS, but it doesn’t actually hide any complexity in the layer below. &lt;strong&gt;You still need to know CSS.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This might be unfair to Tailwind. To my knowledge, the team has never promoted it as a CSS replacement. At its core, it really is just a set of class names that apply styles. But even after working with it for months, there’s still a mental translation layer between “Tailwind CSS” and “real CSS”.&lt;/p&gt;
&lt;p&gt;These issues don’t mean Tailwind is bad. They’re just some of the tradeoffs you inevitably encounter when using a tool.&lt;/p&gt;
&lt;p&gt;Maybe you really want to avoid coming up with names. Maybe you want to see your styles inside your markup, or use a ready-made set of design tokens. Tailwind will let you do those things. But the price you pay is that you need to know exactly how this tool interacts with CSS.&lt;/p&gt;
&lt;p&gt;To me, that trade is a dealbreaker. It increases the number of tools I use without really giving me anything in return. It’s so leaky that I have to constantly think about the thing it’s supposed to abstract away. Yes, it’s nice to have my styles in the same file as the rest of my component code. But that convenience is heavily outweighed by the overhead of actually using Tailwind.&lt;/p&gt;
</content:encoded></item></channel></rss>