<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Ross Zurowski</title>
        <link>https://rosszurowski.com</link>
        <description>Recent blog posts by Ross Zurowski</description>
        <lastBuildDate>Mon, 13 Jul 2026 15:51:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>rosszurowski/v1</generator>
        <copyright>© 2026 Ross Zurowski</copyright>
        <item>
            <title><![CDATA[Makefiles for Web Work]]></title>
            <link>https://rosszurowski.com/log/2022/makefiles</link>
            <guid>https://rosszurowski.com/log/2022/makefiles</guid>
            <pubDate>Sat, 31 Dec 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><code>make</code> is a build tool that’s been around since the 1970s. It was originally designed for automating the building of C programs: installing dependencies, running tests, and compiling binaries.</p>
<p>These days, web projects involve many of the same steps: installing node_modules, running linters and tests, starting dev servers, and compiling files with esbuild or Rollup.</p>
<p>The default choice for automating these steps is often <a href="https://docs.npmjs.com/cli/v6/using-npm/scripts" rel="nofollow noreferrer" target="_blank">npm/yarn scripts</a>: little shell commands written into your project’s <code>package.json</code> file. More complex projects sometimes evolve into using tools like Gulp/Grunt, or even full-blown Docker builds.</p>
<p>But I find <code>make</code> often fills many of the same needs without as much fuss.</p>
<p>Its age and simplicity means it gets a lot of things right:</p>
<ul>
<li><strong>It’s already available everywhere</strong>. Most systems install <code>make</code> when you first set up developer tools. There’s rarely extra steps to get it working.</li>
<li><strong>It’s fast</strong>. People <a href="https://twitter.com/jarredsumner/status/1557694790359085057?s=20&amp;t=R7w-EaxCLuhMhVVaFg_M1g" rel="nofollow noreferrer" target="_blank">routinely point out</a> that npm/yarn scripts are <a href="https://gist.github.com/rosszurowski/1b7971ab2eaf150c5039f3f7ef5e76a0" rel="nofollow noreferrer" target="_blank">shockingly slow to start</a>. <code>make</code> routinely runs commands <strong>~30x faster</strong>, which is nice, because <a href="https://craigmod.com/essays/fast_software/" rel="nofollow noreferrer" target="_blank">fast software is good software</a>.</li>
<li><strong>It’s language-agnostic</strong>. Since <code>make</code> is already installed and works off shell scripts, it doesn’t require commitment to one language or toolchain. You can use it for Go, PHP, Rust, or Node projects equally. And you can shell out to language-specific tools when needed.</li>
<li><strong>It’s simple, with room to grow</strong>. Makefiles have a reputation for getting out-of-hand on large projects. But for smaller projects, the simple file structure, and dependency tracking, and support for multiple commands hits a nice sweet spot.</li>
</ul>
<h3 id="a-consistent-interface-to-project-scripts"><a class="anchor" aria-hidden="true" href="#a-consistent-interface-to-project-scripts"></a>A consistent interface to project scripts</h3>
<p>Thanks to these qualities, <code>make</code> shines as a way to create a consistent interface to commands across projects.</p>
<p>Most projects I work on require some kind of build process. To get to work, I need to re-learn the project’s tools, and the multiple commands needed to download dependencies, build assets, and start a dev server.</p>
<p>Building a site with <a href="https://getkirby.com" rel="nofollow noreferrer" target="_blank">Kirby</a><sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup> I need a mix of <code>php</code>, <code>tailwindcss</code>, <code>esbuild</code>, and <code>rsync</code> commands. For a <a href="https://nextjs.org" rel="nofollow noreferrer" target="_blank">Next.js</a> site, I’m using their built-in CLI. And with <a href="https://fresh.deno.dev/" rel="nofollow noreferrer" target="_blank">Fresh</a>, I don’t even have a <code>package.json</code> file — I’m using <code>deno run</code> and <code>deno fmt</code>. It’s annoying to track these commands down again and refamiliarize myself with where they live: in a JSON file? In a set of script files? In the README?</p>
<p>A Makefile acts as a conventional home for these tools and commands. It’s a single file at the root of the repository, that works with any language, and is easy read/add new commands to.</p>
<p>I leverage this conventionality to create consistent commands between different projects. For example, in every new project, I add a <code>make dev</code> command, which <a href="#auto-installing-node_modules">auto-downloads dependencies</a>, starts a dev server, and watches for changes, no matter the language.</p>
<p>For reference, here’s a list of common commands I’ll add:</p>
<ul>
<li><code>make dev</code> starts a development server with live reloading</li>
<li><code>make build</code> builds a production-ready binary or set of files</li>
<li><code>make deploy</code> tags a release for CI to build, or rsyncs files to a server</li>
<li><code>make format</code> formats all code to a standard style, using <a href="https://prettier.io/" rel="nofollow noreferrer" target="_blank">prettier</a> or <a href="https://go.dev/blog/gofmt" rel="nofollow noreferrer" target="_blank">gofmt</a></li>
<li><code>make lint</code> runs code quality checks, like <a href="https://eslint.org/" rel="nofollow noreferrer" target="_blank">eslint</a> or <a href="https://golangci-lint.run/" rel="nofollow noreferrer" target="_blank">golanglint-ci</a></li>
<li><code>make test</code> runs a full set of test suites. Sometimes I’ll include <code>lint</code> scripts in here so there’s a single command to run in CI</li>
<li><code>make clean</code> removes all build artifacts and downloaded dependencies</li>
<li><code>make help</code> lists all the commands in a Makefile (<a href="#self-documenting-makefiles">discussed here</a>)</li>
</ul>
<p>This “one command” and “one interface” is highly beneficial for teammates too.
They can get started quickly, and if they want to learn more or tinker, the commands and dependency relationships are all documented in a single place.</p>
<h3 id="recipes"><a class="anchor" aria-hidden="true" href="#recipes"></a>Recipes</h3>
<p>Other articles cover the basics of how to write a Makefile in great depth,<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="true" aria-describedby="footnote-label">2</a></sup> so I’ll leave syntax aside and instead focus on sharing some common techniques that I use:</p>
<ul>
<li><a href="#tasks-without-dependencies">Tasks without dependencies</a></li>
<li><a href="#referencing-node_modules-binaries">Referencing node_modules</a></li>
<li><a href="#auto-installing-node_modules">Auto-installing node_modules</a></li>
<li><a href="#skipping-re-runs-of-slow-tasks">Skipping re-runs of slow tasks</a></li>
<li><a href="#using-environment-variables">Using environment variables</a></li>
<li><a href="#configuring-tasks-with-default-variables">Configuring tasks with default variables</a></li>
<li><a href="#self-documenting-makefiles">Self-documenting Makefiles</a></li>
<li><a href="#parallel-dev-servers">Parallel dev servers</a></li>
<li><a href="#hermetic-environments">Hermetic environments</a></li>
</ul>
<hr/>
<h3 id="tasks-without-dependencies"><a class="anchor" aria-hidden="true" href="#tasks-without-dependencies"></a>Tasks without dependencies</h3>
<p>By default, <code>make</code> rule names refer to actual files to be built. If you write a rule like this:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &quot;Hello, world!&quot;</span></span></code></pre></figure>
<p>And then later create a file named <code>dev</code>, <code>make</code> doesn’t run the commands.</p>
<pre><code>$ touch dev
$ make dev
make: &#x27;dev&#x27; is up to date.
</code></pre>
<p>For one-off scripts that you just want to run when you tell them to run, this default is a little annoying.</p>
<p>In these cases, add <code>.PHONY: &lt;name&gt;</code> to the end of the target definition. This tells <code>make</code> to skip the dependency checks and run the command every time:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &quot;Hello, world!&quot;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span></code></pre></figure>
<p>Now:</p>
<pre><code>$ touch dev
$ make dev
Hello, world!
$ make dev
Hello, world!
</code></pre>
<p>I usually bundle the <code>.PHONY</code> line directly with the command it’s associated with, which makes it easy to tell which commands are “tasks” and which ones are instructions for building files:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules </span><span style="color:var(--shiki-token-comment)">## Start a dev server</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next dev</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">lint</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules </span><span style="color:var(--shiki-token-comment)">## Lint files for code quality</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next lint</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> lint</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">format</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules </span><span style="color:var(--shiki-token-comment)">## Format code to a standard style</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/eslint --fix &#x27;src/**/*.{js,jsx,ts,tsx}&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/prettier --write &#x27;src/**/*.{js,jsx,ts,tsx}&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> format</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span></code></pre></figure>
<h3 id="referencing-node_modules-binaries"><a class="anchor" aria-hidden="true" href="#referencing-node_modules-binaries"></a>Referencing node_modules binaries</h3>
<p>In web projects, you’ll often want to reference tools installed via <code>yarn</code> or <code>npm</code>, like <code>next</code>, <code>prettier</code>, or <code>eslint</code>. In npm/yarn scripts, you reference these utilities by name:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="json" data-theme="css-variables">package.json</figcaption><pre tabindex="0" data-language="json" data-theme="css-variables"><code data-language="json" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-foreground)">{</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">  &quot;scripts&quot;</span><span style="color:var(--shiki-token-punctuation)">:</span><span style="color:var(--shiki-foreground)"> {</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">    &quot;format&quot;</span><span style="color:var(--shiki-token-punctuation)">:</span><span style="color:var(--shiki-token-string-expression)"> &quot;prettier --write &#x27;src/**/*.{js,jsx,ts,tsx}&#x27;&quot;</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">  }</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">}</span></span></code></pre></figure>
<p>Under the hood, these scripts are stored in the <code>node_modules/.bin</code> directory. When you run <code>yarn dev</code>, it adds that directory to your <code>$PATH</code>, so running <code>yarn next</code> refers to the local installation instead of any global ones.</p>
<p>In Makefiles, we can reference these tools by being explicit. Write <code>./node_modules/.bin/&lt;name&gt;</code> before commands you want to use. So instead of <code>prettier</code>, you write:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">format</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/prettier --write &#x27;src/**/*.{js,jsx,ts,tsx}&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> format</span></span></code></pre></figure>
<p>This explicitness has the benefit of throwing an error if the local <code>prettier</code> isn’t installed (but it should be if you <a href="#auto-installing-node_modules">set up node_modules as a dependency</a>).</p>
<p>There are techniques to modify your <code>$PATH</code> inside Makefiles, but they don’t seem to work consistently with the version of <code>make</code> that ships with macOS.<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref="true" aria-describedby="footnote-label">3</a></sup> Better to keep things simple.</p>
<h3 id="auto-installing-node_modules"><a class="anchor" aria-hidden="true" href="#auto-installing-node_modules"></a>Auto-installing node_modules</h3>
<p>When working with teammates unfamiliar with Node, I’ll sometimes help troubleshoot errors like this:</p>
<blockquote>
<p>I ran <code>yarn start</code> and got this error:</p>
<pre><code>$ yarn start
$ run-p start:*
/bin/sh: run-p: command not found
error Command failed with exit code 127.
</code></pre>
</blockquote>
<p>This message means they tried running <code>yarn start</code> without running <code>yarn install</code> first. But it’s not terribly clear that’s how to resolve the error unless you know what you’re looking for.</p>
<p>Instead, with <code>make</code>, we can express the relationship between all these dependent tasks at once so we don’t need to think about them again.</p>
<p>For example, if we create a task to build node_modules when package.json changes, we can leverage that in all the other scripts:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next dev</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">build</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next build</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> build</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span></code></pre></figure>
<p>Running <code>make dev</code> will install node_modules first, then run the <code>dev</code> command after. And on subsequent runs, it’ll skip the install step and just run the dev command.</p>
<h3 id="skipping-re-runs-of-slow-tasks"><a class="anchor" aria-hidden="true" href="#skipping-re-runs-of-slow-tasks"></a>Skipping re-runs of slow tasks</h3>
<p>For slow tasks, you can save time on subsequent runs by adding known dependencies to your rules.</p>
<p>For example, when working on a Next.js app locally, Next runs data fetching functions like <code>getStaticProps</code> on every page navigation. Sometimes, when I’m working on a feature that requires me to navigate between pages quickly (like transitions between pages), I run a production build to avoid the delay of the data fetching when previewing my work.</p>
<p>With <code>make</code>, I do this by defining the dependent files and folders, which we should rebuild the output for when they change. For this Next.js example, it means we can run a production build and start up the server. When running the command again, if the production build is up-to-date, we can just start up the server without the delay of rebuilding.</p>
<p>In order to get a list of all the files in a directory, we can use the <code>$(shell &lt;cmd&gt;)</code> expression and the <code>find</code> command:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">start</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules .next</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next start</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> start</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">.next</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules next.config.js </span><span style="color:var(--shiki-token-string-expression)">$(</span><span style="color:var(--shiki-token-function)">shell</span><span style="color:var(--shiki-token-string-expression)"> find src -type f -name &quot;</span><span style="color:var(--shiki-token-constant)">*</span><span style="color:var(--shiki-token-string-expression)">.ts&quot; -o -name &quot;</span><span style="color:var(--shiki-token-constant)">*</span><span style="color:var(--shiki-token-string-expression)">.tsx&quot;)</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next build</span></span></code></pre></figure>
<p>Now running <code>make start</code>:</p>
<pre><code>$ make start
# ... next.js build output
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
</code></pre>
<p>Running it again skips the build and just starts the server, since the files haven’t changed.</p>
<pre><code>$ make start
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
</code></pre>
<p>You can add different file extensions by adding more instances of <code>-o -name &quot;*.&lt;ext&gt;&quot;</code>. To add CSS files to the mix:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">.next</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules next.config.js </span><span style="color:var(--shiki-token-string-expression)">$(</span><span style="color:var(--shiki-token-function)">shell</span><span style="color:var(--shiki-token-string-expression)"> find src -type f -name &quot;</span><span style="color:var(--shiki-token-constant)">*</span><span style="color:var(--shiki-token-string-expression)">.ts&quot; -o -name &quot;</span><span style="color:var(--shiki-token-constant)">*</span><span style="color:var(--shiki-token-string-expression)">.tsx&quot; -o -name &quot;</span><span style="color:var(--shiki-token-constant)">*</span><span style="color:var(--shiki-token-string-expression)">.css&quot;)</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next build</span></span></code></pre></figure>
<h3 id="using-environment-variables"><a class="anchor" aria-hidden="true" href="#using-environment-variables"></a>Using environment variables</h3>
<p>To use env variables you’ve defined in a local file, like a <code>.env</code>, you can
use the following block at the top of your Makefile:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-keyword)">include</span><span style="color:var(--shiki-foreground)"> .env</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">export</span></span></code></pre></figure>
<p>I find this makes it easy to store secret token for one-off tasks like API calls or just avoiding installing a dotenv dependency into my projects:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="shell" data-theme="css-variables">.env</figcaption><pre tabindex="0" data-language="shell" data-theme="css-variables"><code data-language="shell" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-foreground)">SSH_USER</span><span style="color:var(--shiki-token-keyword)">=</span><span style="color:var(--shiki-token-string-expression)">&quot;user&quot;</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">SSH_HOST</span><span style="color:var(--shiki-token-keyword)">=</span><span style="color:var(--shiki-token-string-expression)">&quot;example.com&quot;</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">SSH_DIR</span><span style="color:var(--shiki-token-keyword)">=</span><span style="color:var(--shiki-token-string-expression)">&quot;public_html&quot;</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-keyword)">include</span><span style="color:var(--shiki-foreground)"> .env</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">export</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">content</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-comment)">  ## Sync content from the server to the local environment.</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">rsync -avz --delete &quot;</span><span style="color:var(--shiki-token-string-expression)">$(SSH_USER)</span><span style="color:var(--shiki-foreground)">@</span><span style="color:var(--shiki-token-string-expression)">$(SSH_HOST)</span><span style="color:var(--shiki-foreground)">:</span><span style="color:var(--shiki-token-string-expression)">$(SSH_DIR)</span><span style="color:var(--shiki-foreground)">/content/&quot; content/</span></span></code></pre></figure>
<h3 id="configuring-tasks-with-default-variables"><a class="anchor" aria-hidden="true" href="#configuring-tasks-with-default-variables"></a>Configuring tasks with default variables</h3>
<p>Using <code>?=</code> you can set default variables that you can override outside of <code>make</code>. This lets you expose some configuration options to Makefile users.</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-foreground)">PORT </span><span style="color:var(--shiki-token-keyword)">?=</span><span style="color:var(--shiki-foreground)"> 9000</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">php -S localhost:</span><span style="color:var(--shiki-token-string-expression)">$(PORT)</span><span style="color:var(--shiki-foreground)"> index.php</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span></code></pre></figure>
<p>Now you can customize the port (while falling back to a default value) like so:</p>
<pre><code>$ PORT=2000 make dev
PHP 8.2.1 Development Server (http://localhost:2000) started
</code></pre>
<h3 id="self-documenting-makefiles"><a class="anchor" aria-hidden="true" href="#self-documenting-makefiles"></a>Self-documenting Makefiles</h3>
<p>Makefiles can grow to contain lots of commands over time. One trick I’ve seen is to use this <code>make help</code> target to document important scripts:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">help</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-comment)"> ## Show this help</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &quot;\nSpecify a command. The choices are:\n&quot;</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">grep -E &#x27;^[0-9a-zA-Z_-]+:.*?## .*$$&#x27; </span><span style="color:var(--shiki-token-string-expression)">$(MAKEFILE_LIST)</span><span style="color:var(--shiki-foreground)"> | awk &#x27;BEGIN {FS = &quot;:.*?## &quot;}; {printf &quot;  \033[0;36m%-12s\033[m %s\n&quot;, $$1, $$2}&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &quot;&quot;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> help</span></span></code></pre></figure>
<p>Using this, you can document commands with a <code>## comment</code> at the end of a line. For example:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules </span><span style="color:var(--shiki-token-comment)">## Start a local development server</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next dev</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">format</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules </span><span style="color:var(--shiki-token-comment)">## Format all source files</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/prettier --write &#x27;src/**/*.{ts,tsx}&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> format</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span></code></pre></figure>
<p>Adding this target means running <code>make help</code> will list all the targets labelled with the <code>## comment</code>. Use this to call out important commands for yourself or your teammates.</p>
<pre><code>$ make help

Specify a command. The choices are:

  dev          Start a local development server
  format       Format all source files
  clean        Clean all built files
  help         Show this help
</code></pre>
<h3 id="parallel-dev-servers"><a class="anchor" aria-hidden="true" href="#parallel-dev-servers"></a>Parallel dev servers</h3>
<p>I’ll often want to run multiple long-lived processes while working on a project. For example, a recent website I worked on uses:</p>
<ul>
<li>A <code>php -S</code> dev server</li>
<li>A <code>tailwindcss --watch</code> process</li>
<li>An <code>esbuild --watch</code> process</li>
</ul>
<p>Unfortunately, like npm/yarn scripts, <code>make</code> doesn’t have meaningful tools for running long-lived parallel tasks. There <em>are</em> technically a few ways you could get parallel servers running, but each technique gets a little fiddly for my tastes:</p>
<ul>
<li>Using <code>make -j3 &lt;a&gt; &lt;b&gt; &lt;c&gt;</code> doesn’t treat processes as a group. So if one subprocess fails (eg. a syntax error stops the Tailwind <code>--watch</code> process), the others keep on going. You lose your live-updating changes without noticing until things are broken.</li>
<li>Shell background jobs with <code>&lt;command&gt; &amp;</code> suffer the same process group issue, plus can sometimes leave processes hanging around after you Ctrl+C out.</li>
<li>External binaries like GNU’s <code>parallel</code> command, or <code>foreman</code> work, but they need separate installation steps.</li>
</ul>
<p>Thankfully, <code>make</code> doesn’t care: we can just pop into language-specific tools that are suitable for the job. In Go projects, I’ll use a custom Go script. In Node projects, I’ll use the <a href="https://www.npmjs.com/package/concurrently" rel="nofollow noreferrer" target="_blank">concurrently</a> npm package:</p>
<pre><code>yarn add --dev concurrently
</code></pre>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-foreground)">PORT </span><span style="color:var(--shiki-token-keyword)">?=</span><span style="color:var(--shiki-foreground)"> 9000</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/concurrently \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		&#x27;php -S localhost:$PORT index.php&#x27; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		&#x27;./node_modules/.bin/esbuild src/*.ts --bundle --outdir=assets/ --watch&#x27; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		&#x27;./node_modules/.bin/tailwindcss -i src/index.css -o assets/index.css --watch&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span></code></pre></figure>
<p>You still get the nice <code>make dev</code> interface and dependency tracking, but proper parallel dev server handling behind-the-scenes.</p>
<p><strong>Update:</strong> as of recently, I’ve been using <a href="https://github.com/rosszurowski/tandem" rel="nofollow noreferrer" target="_blank">tandem</a>, a tool I built for running multiple commands as a process group. You pass it multiple commands and it runs them in parallel, shutting down the whole group if one fails.</p>
<p>It’s designed to be easily embedded in Makefiles with a one-line installation script. Add <code>.cache</code> to your <code>.gitignore</code> file, and use the following block:</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-foreground)">PORT </span><span style="color:var(--shiki-token-keyword)">?=</span><span style="color:var(--shiki-foreground)"> 9000</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> .cache/tandem node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">.cache/tandem \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		&#x27;php -S localhost:$PORT index.php&#x27; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		&#x27;esbuild src/*.ts --bundle --outdir=assets/ --watch&#x27; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		&#x27;tailwindcss -i src/index.css -o assets/index.css --watch&#x27;</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">.cache/tandem</span><span style="color:var(--shiki-token-keyword)">:</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">mkdir -p $$(dirname $@)</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">curl -fsSL https://raw.githubusercontent.com/rosszurowski/tandem/main/install.sh | bash -s -- --dest=&quot;$$(dirname $@)&quot;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span></code></pre></figure>
<p>The first time you run <code>make dev</code>, it’ll download and cache it for future uses.</p>
<h3 id="hermetic-environments"><a class="anchor" aria-hidden="true" href="#hermetic-environments"></a>Hermetic environments</h3>
<p>Building on <a href="#auto-installing-node_modules">the auto-installing node_modules technique</a>: you can get fancy with dependencies to download local versions of your <em>entire</em> toolchain. This way everyone runs the same version of everything, without the slowness of using Docker.</p>
<p>This snippet reads yarn and node versions from files called <code>yarn.rev</code> and <code>node.rev</code>, downloads local copies into the <code>tool</code>, and uses those instances for all your scripts.</p>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="text" data-theme="css-variables">node.rev</figcaption><pre tabindex="0" data-language="text" data-theme="css-variables"><code data-language="text" data-theme="css-variables" style="display:grid"><span data-line=""><span>16.13.1</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="text" data-theme="css-variables">yarn.rev</figcaption><pre tabindex="0" data-language="text" data-theme="css-variables"><code data-language="text" data-theme="css-variables" style="display:grid"><span data-line=""><span>1.22.19</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><figcaption data-rehype-pretty-code-title="" data-language="makefile" data-theme="css-variables">Makefile</figcaption><pre tabindex="0" data-language="makefile" data-theme="css-variables"><code data-language="makefile" data-theme="css-variables" style="display:grid"><span data-line=""><span style="color:var(--shiki-token-keyword)">export</span><span style="color:var(--shiki-foreground)"> CACHE_PATH </span><span style="color:var(--shiki-token-keyword)">:=</span><span style="color:var(--shiki-foreground)"> .cache</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">export</span><span style="color:var(--shiki-foreground)"> PATH </span><span style="color:var(--shiki-token-keyword)">:=</span><span style="color:var(--shiki-foreground)"> ./tool:</span><span style="color:var(--shiki-token-string-expression)">$(PATH)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">dev</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node_modules </span><span style="color:var(--shiki-token-comment)">## Run a local development server</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">./node_modules/.bin/next dev</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dev</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">clean</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-comment)"> ## Clean all build artifacts</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">rm -rf ./tool</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">rm -rf ./cache</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">rm -rf ./node_modules</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">.PHONY</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> clean</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-function)">node_modules</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> package.json tool/yarn tool/node</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">yarn install</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-comment)"># Reads a version number from a file called `node.rev`</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">tool/node</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> node.rev</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">mkdir -p tool</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">mkdir -p </span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/node</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-token-string-expression)">$(</span><span style="color:var(--shiki-token-function)">eval</span><span style="color:var(--shiki-token-string-expression)"> OS=$(</span><span style="color:var(--shiki-token-function)">shell</span><span style="color:var(--shiki-token-string-expression)"> uname -s | tr A-Z a-z))</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-token-string-expression)">$(</span><span style="color:var(--shiki-token-function)">eval</span><span style="color:var(--shiki-token-string-expression)"> ARCH=$(</span><span style="color:var(--shiki-token-function)">shell</span><span style="color:var(--shiki-token-string-expression)"> uname -m | sed -e &quot;s/x86_64/x64/&quot; | sed -e &quot;s/aarch64/arm64/&quot;))</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">read -r REV &lt;$&lt; &amp;&amp; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		cd </span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/node &amp;&amp; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		curl -L -o node.tar.gz https://nodejs.org/dist/v$$REV/node-v$$REV-</span><span style="color:var(--shiki-token-string-expression)">$(OS)</span><span style="color:var(--shiki-foreground)">-</span><span style="color:var(--shiki-token-string-expression)">$(ARCH)</span><span style="color:var(--shiki-foreground)">.tar.gz &amp;&amp; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		tar --strip-components=1 -xzf node.tar.gz</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &quot;#!/bin/sh&quot; &gt; $@</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &#x27;exec /usr/bin/env PATH=&quot;</span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/node:$$PATH&quot; &quot;</span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/node/bin/node&quot; &quot;$$@&quot;&#x27; &gt;&gt; $@</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">chmod +x $@</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:var(--shiki-token-comment)"># Reads a version number from a file called `yarn.rev`</span></span>
<span data-line=""><span style="color:var(--shiki-token-function)">tool/yarn</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> yarn.rev</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">mkdir -p tool</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">mkdir -p </span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/yarn</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">read -r REV &lt;$&lt; &amp;&amp; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		cd </span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/yarn &amp;&amp; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		curl -L -o yarn.tar.gz https://github.com/yarnpkg/yarn/releases/download/v$$REV/yarn-v$$REV.tar.gz &amp;&amp; \</span></span>
<span data-line=""><span style="color:var(--shiki-foreground)">		tar --strip-components=1 -xzf yarn.tar.gz</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &quot;#!/bin/sh&quot; &gt; $@</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">echo &#x27;exec /usr/bin/env PATH=&quot;</span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/yarn:$$PATH&quot; &quot;</span><span style="color:var(--shiki-token-string-expression)">$(CACHE_PATH)</span><span style="color:var(--shiki-foreground)">/yarn/bin/yarn&quot; &quot;$$@&quot;&#x27; &gt;&gt; $@</span></span>
<span data-line=""><span style="color:var(--shiki-token-keyword)">	@</span><span style="color:var(--shiki-foreground)">chmod +x $@</span></span></code></pre></figure>
<p>This example probably pushes the limit of what should go directly in a Makefile vs. being broken out into a separate shell script or tool, but hopefully it demonstrates the extent to which you can automate your dev environment with Makefiles.</p>
<p>We use something similar to this <a href="https://tailscale.com" rel="nofollow noreferrer" target="_blank">at Tailscale</a>, and it greatly simplifies getting new teammates started, and upgrading versions of core dependencies like Go, Node, or Yarn.</p>
<hr/>
<h3 id="limitations"><a class="anchor" aria-hidden="true" href="#limitations"></a>Limitations</h3>
<p>While <code>make</code> is great for many projects, it’s not always the right thing to reach for. A few cases where you should avoid it:</p>
<ul>
<li><strong>If you’re working with Windows.</strong> Makefiles usually rely on a lot of UNIX tools and conventions, like <code>ENV=val</code> environment variables, or <code>awk</code>, <code>sed</code>, and <code>grep</code>, which don’t work on Windows. It’s possible to make cross-platform Makefiles, but if this is important to you, it may be better to use a tool that abstracts away platform differences.</li>
<li><strong>If you’re automating complex builds.</strong> <code>make</code> gives you simple tools for simple builds. With involved chains of dependencies, Makefiles get unwieldly fast. If you start mucking with file modification timestamps, dealing with weird shell quoting rules or anything to do with automake, you’ve probably gone too far, and would be better served by another tool.</li>
<li><strong>If you already have a setup you’re happy with</strong>. For simple projects, npm/yarn scripts can be enough! Don’t change if you’ve got something working. That said, I find Make’s speed, simplicity, and little bit of extra flexibility helpful, and suggest you give it a try.</li>
</ul>
<h3 id="reference-makefiles"><a class="anchor" aria-hidden="true" href="#reference-makefiles"></a>Reference Makefiles</h3>
<ul>
<li>The <a href="https://github.com/stripe/stripe-cli/blob/master/Makefile" rel="nofollow noreferrer" target="_blank">stripe-cli</a> has some great snippets, including a <code>// TODO:</code> comment finder, a Git tag generator, and Git hook installation step.</li>
<li>This <a href="https://github.com/tailscale/docker-extension/blob/main/Makefile" rel="nofollow noreferrer" target="_blank">Tailscale docker desktop extension</a> shows using a Makefile to make a nicer dev experience. Rather than manually building containers, installing them, and configuring dev server options, I composed them into some commands that made working on the project a lot easier.</li>
<li>This <a href="https://gist.github.com/rosszurowski/24bdc2cce3bd440e6210c9c7d7164745" rel="nofollow noreferrer" target="_blank">Kirby CMS Makefile</a> which has commands to update a vendored dependency, sync content with a remote server, and deploy via rsync to a PHP server.</li>
</ul>
<p>Know other good web-centric Makefiles? <a href="mailto:ross@rosszurowski.com">Let me know!</a></p>
<section data-footnotes="true" class="footnotes"><h2 class="sr-only" id="footnote-label"><a class="anchor" aria-hidden="true" href="#footnote-label"></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>Here’s a <a href="https://gist.github.com/rosszurowski/24bdc2cce3bd440e6210c9c7d7164745" rel="nofollow noreferrer" target="_blank">more complete Makefile example for Kirby</a> if you’re interested in seeing how this approach expands. <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>I’ve found <a href="https://earthly.dev/blog/make-tutorial/" rel="nofollow noreferrer" target="_blank">this article</a> and <a href="https://www.olioapps.com/blog/the-lost-art-of-the-makefile/" rel="nofollow noreferrer" target="_blank">this one</a> to be decent introductions to the Makefile syntax. <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>More details about the specific weirdness are <a href="https://gist.github.com/rosszurowski/eee05cebb67fd869b7db1914b844db9c" rel="nofollow noreferrer" target="_blank">in this gist</a>. <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[On Small Seasons and Long Calendars]]></title>
            <link>https://rosszurowski.com/log/2018/small-seasons-long-calendars</link>
            <guid>https://rosszurowski.com/log/2018/small-seasons-long-calendars</guid>
            <pubDate>Tue, 15 May 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="/log/2018/small-seasons-long-calendars/almanac.jpg"/><link rel="preload" as="image" href="/log/2018/small-seasons-long-calendars/sad-calendar.png"/><p>Recently, I felt an odd longing for seasons.</p>
<p>San Francisco has only two: pleasant and foggy. Trees keep their leaves year-round, and temperatures are consistently in the sweater range. It’s a city of eternal mild weather.</p>
<p>This past December I went back to Toronto when winter was in full force. My phone told me it was -30°C. The downtown core was like a wind tunnel, blasting my face with high-speed frigid air. <em>Why does anyone put up with this?</em> I thought. But here I am, five months later, sitting in the California sun, longing for some crappier weather.</p>
<p>It’s not the Canadian winters that I miss so much as the way seasons naturally divide the year.</p>
<p>Living in a season-less city makes time move at a scary pace, like how as a kid, summer vacation would end before you know it. Between work, travel, and a few personal projects, weeks pass by in an instant. I’ve been here for two years, but it’s only felt like a couple months.</p>
<hr/>
<figure><img src="/log/2018/small-seasons-long-calendars/almanac.jpg" alt="A painting of a farmer consulting the local almanac." width="822" height="624"/></figure>
<p>Few tools have been as important to humanity as calendars. Shortly after writing systems developed, calendars were found. It makes sense: in agricultural days it was important to know when in the year to plough, to seed, to harvest and when to let fields lie fallow.</p>
<p>Every culture developed their own local calendars to track the time, often sharing the definition of day (an easy one), but varying from one region to the next in concepts like weeks, months, and years.</p>
<p>As societies urbanized, these measurements were standardized. Over time weeks and months were defined and consolidated into either a moon-phase-based <em>lunisolar calendar</em>, or a sun-path based <em>solar calendar</em> (the latter being the origin for the Gregorian calendar we use today). Standardizing weeks and months was important: by having a shared language for time, you can now talk with the town over about if winter is coming early this year, or when the next holy day is, or even just speak meaningfully about the future.</p>
<p>There’s no shortage of writing on our mechanization of time, but McLuhan is particularly concise:</p>
<blockquote>
<p>The clock [or calendar, in our case] dragged man out of the world of seasonal rhythms and recurrence, as effectively as the alphabet had released him from the magical resonance of the spoken word and the tribal trap.</p>
</blockquote>
<p>Like written language, the development of a standard calendar enabled stronger coordination and control for countries and societies. And <em>also</em> like written language, standard calendars introduced shared metaphors and patterns of thought. The week, the month, the year, sure. But also the financial quarter, the nine-to-five, the holiday.</p>
<p>This language shapes how we think about time and hence how we think about our lives and work.</p>
<p>For instance, my roommates work at larger companies, and have projects which are constrained to the current financial quarter. They often come home frustrated, feeling like they can’t make meaningful progress because of the short cadence. It encourages decisions that quickly cater to metrics, rather than working towards about more meaningful, holistic changes. The chunks of time are too small, too constraining.</p>
<p>The inverse happens every New Year’s Eve. People set resolutions that are achievable within a year, but as the days pass, lose themselves in how quickly they go by. The cadence for reflection and review is often too long, and the goals become disconnected from everyday experience, nebulous and intangible.</p>
<p>Both of these experiences point to, in part, a failure of how we conceptualize and represent time. Using years and quarters to divide time often feels quite … arbitrary.</p>
<p>Standardized calendars are no doubt great for accounting, communication, and coordination<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup> but they fail at more personal functions like doing deep work, or maintaining cycles of interest and curiosity.</p>
<h3 id="phases"><a class="anchor" aria-hidden="true" href="#phases"></a>”Phases”</h3>
<p>A friend of mine has a unique method of journaling.</p>
<p>He documents moments from his life via a mishmash of words, events, ideas, and screenshots, and groups them into discrete <em>phases</em> in iPhoto. Each phase is roughly two weeks long — but it’s defined by his living situation and activities, not the days of the month. A week-long bike trip across Southern Europe might be one phase, but two months of heads-down focus on school work could be another.</p>
<p>It’s a great way to remember things. In conversations, a topic or feeling will come up, only for him to say, “oh man, that was <em>so</em> phase 37.” By chunking his life out into phases it becomes easier to grasp, both in language and in memory.</p>
<p>Contrast that with a more standard journaling experience: writing a couple hundred words every few days. At the end of a month or a year, you look back on a sea of sentences, unfiltered and uncategorized. So much is written that it’s hard to extract anything meaningful from it without jumping around to specific days.</p>
<p>By eschewing a standard period for a self-defined one, my friend has inadvertently created a personal unit of time.</p>
<h3 id="thinking-in-decades"><a class="anchor" aria-hidden="true" href="#thinking-in-decades"></a>Thinking in Decades</h3>
<p>Consider another function of calendars: planning.</p>
<p>You open up Google Calendar and you get a view of the next 7 days. You can block out the next 168 hours of your life in 30 minute events.</p>
<figure><img src="/log/2018/small-seasons-long-calendars/sad-calendar.png" alt="Google Calendar&#x27;s UI, with a view of seven days and 30-minute slots for scheduling. Not particularly inspiring." width="2440" height="1650"/></figure>
<p>But unless your life is a long series of <a href="http://www.paulgraham.com/makersschedule.html" rel="nofollow noreferrer" target="_blank">back-to-back 30 minute meetings</a>, does this interface make sense? Do most people actually live that way?</p>
<p>In contrast, James Fisher’s <a href="https://jameshfisher.com/2017/06/06/long-calendar" rel="nofollow noreferrer" target="_blank">long calendar idea</a> sounds really compelling:</p>
<blockquote>
<p>How long will you be in your current house? How do you plan out your thirties? When will you move to Spain, as you’ve always imagined you would? When might you have your first child, and what job would you like to aim for by then? […] <br/><br/>
These life questions require a different kind of calendar. They operate on a different timescale, where the month is probably the most granular unit of time you might want. These questions work on a personal human epoch — “I am 29”, not “it is 2017.”</p>
</blockquote>
<p>How might seeing your whole life, laid out on a single surface affect how you live and plan?</p>
<h3 id="small-seasons"><a class="anchor" aria-hidden="true" href="#small-seasons"></a>Small Seasons</h3>
<p>In Kenya Hara’s book <em>White</em>, he briefly talks about <em>sekki</em>, small seasons used in ancient agricultural China and Japan to divide the year. Each sekki corresponds to a shift in the climate, not the calendar, with poetic language to match the changes.</p>
<p>Where spring is our “large” season, lasting roughly from March to June, the sekki calendar breaks it down into six small seasons: <em>start of spring</em>, <em>rain waters</em>, <em>the going-out of the worms</em>, <em>vernal equinox</em>, <em>clear and bright</em>, and <em>rain for harvests</em>.<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="true" aria-describedby="footnote-label">2</a></sup> Both describe a similar period of time, but one does so with a precision that feels much more human.</p>
<p>These days, I increasingly find myself using sekki as a calendar.</p>
<p>On one hand, it’s nice to have a calendar so explicitly based on the surrounding environment; you’re encouraged to look up and notice what’s different today, that the crickets are no longer chirping and that time has gone by.</p>
<p>But it also better matches my own ebb and flow of interest, attention, and general mental headspace. Two weeks of deep focus on something is a healthy amount before I want to move on to a different aspect, or a new project all together.</p>
<hr/>
<p>Sekki, phases, and the long calendar are just a few examples of more human and purposeful calendaring systems. Heck, even <a href="https://cuesa.org/eat-seasonally/charts/vegetables" rel="nofollow noreferrer" target="_blank">a seasonal vegetable chart</a> for an area is an unexpectedly useful time-keeping tool. Why shouldn’t your calendar help you decide when to make a minestrone soup, oxtail stew, or a pico de gallo?</p>
<p>You can imagine some bigger questions along the same train of thought:</p>
<ul>
<li>How can calendars inspire or inform daily living?</li>
<li>What does a communal calendar look like? How can it afford community action and a sense of collective identity?</li>
<li>How can calendars encourage thinking beyond our own lifespans? (Peter Bïlak’s <a href="https://www.typotheque.com/posters/the_100-year_calendar" rel="nofollow noreferrer" target="_blank">100-year calendar</a> is an interesting example of this)</li>
</ul>
<p>I’m interested in more conversation and links about this.<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref="true" aria-describedby="footnote-label">3</a></sup> It seems like there’s merit to finding more personal ways of chunking out time. Especially in an era with such powerful means to represent time, finding novel and more meaningful timescales seems like fertile ground for exploration.</p>
<p>But, if nothing else, I’m happier just to notice the time passing a little more often.</p>
<h3 id="further-reading"><a class="anchor" aria-hidden="true" href="#further-reading"></a>Further reading</h3>
<ul>
<li><a href="https://smallseasons.guide/" rel="nofollow noreferrer" target="_blank">Small Seasons</a></li>
<li><a href="http://i-s-o-p-t.com/" rel="nofollow noreferrer" target="_blank">In Search of Personalized Time</a></li>
<li><a href="https://a-line-moving-across-a-window-once-every-year.com/" rel="nofollow noreferrer" target="_blank">A Line Moving Across A Window Once Every Year</a></li>
<li><a href="https://austinkleon.com/tag/seasons/" rel="nofollow noreferrer" target="_blank">Austin Kleon’s notes on seasons</a>, including <a href="https://austinkleon.com/2017/10/02/seasonal-time/" rel="nofollow noreferrer" target="_blank">Seasonal Time</a></li>
</ul>
<section data-footnotes="true" class="footnotes"><h2 class="sr-only" id="footnote-label"><a class="anchor" aria-hidden="true" href="#footnote-label"></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>Having recently experienced the joys of programming around <a href="https://en.wikipedia.org/wiki/Ethiopian_calendar" rel="nofollow noreferrer" target="_blank">the Ethiopian calendar</a>, I’m <em>enthusiastically</em> in support of alignment to conventions when working with others. <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>You can find a <a href="https://smallseasons.guide" rel="nofollow noreferrer" target="_blank">full list of small seasons here</a>, a small site built to enshrine this idea. <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>If you are too, drop some hot links in this <a href="https://www.are.na/ross-zurowski/alternate-calendars" rel="nofollow noreferrer" target="_blank">Alternate Calendars</a> Are.na channel. I’d love to see more. <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Moving Bodies at Speed]]></title>
            <link>https://rosszurowski.com/log/2017/bodies-at-speed</link>
            <guid>https://rosszurowski.com/log/2017/bodies-at-speed</guid>
            <pubDate>Wed, 25 Oct 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Earlier this year, Elon Musk <a href="https://twitter.com/elonmusk/status/888053175155949572" rel="nofollow noreferrer" target="_blank">announced he got verbal approval</a> to build an underground Hyperloop between New York and D.C.</p>
<p>Travel is one of those perennial human problems, and over the years we’ve devised <a href="https://techrato.com/wp-content/uploads/2017/04/the_boring_company_concept_3.gif" rel="nofollow noreferrer" target="_blank">increasingly complex solutions</a> to move people and things from one place to another. Bikes, trains, cars, planes, and now roller-coasters inside tunnels. Each new approach lets us travel quicker than before, but also requires more maintenance, safety equipment, and regulations.</p>
<hr/>
<p>In university I was introduced to the writings of Paul Virilio, a French cultural theorist. One of his most known ideas is <a href="https://en.wikipedia.org/wiki/Paul_Virilio#The_integral_accident" rel="nofollow noreferrer" target="_blank">the integral accident</a>, which is often summarized in this quote:</p>
<blockquote>
<p>When you invent the ship, you also invent the shipwreck; when you invent the plane you also invent the plane crash.</p>
</blockquote>
<p>It’s a meaty idea that digs into humanity’s infatuation with technology. But when I hear it, all I can ever think is what it means for objects moving at high speeds.</p>
<p>Moving faster <em>inherently</em> creates more risk. Even when the risks are well-controlled (plane rides are statistically safer than driving, right?) they are never gone. A rocket will always have more dangerous <em>potential</em> than a car.</p>
<p>Greater velocity equals greater consequences when things go wrong. There’s no getting around that nature, no matter how many safeguards you implement.</p>
<p>It’s always interesting to see innovative approaches to age old problems. But, in my head, complex solutions never seem <a href="https://en.wikipedia.org/wiki/Appropriate_technology" rel="nofollow noreferrer" target="_blank">quite as appropriate</a> as ways to use simpler ones, like walking, biking, and creating more dense urban spaces.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Toward a Distributed Web]]></title>
            <link>https://rosszurowski.com/log/2017/toward-a-distributed-web</link>
            <guid>https://rosszurowski.com/log/2017/toward-a-distributed-web</guid>
            <pubDate>Tue, 10 Oct 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="/log/2017/toward-a-distributed-web/awh-snap.svg"/><link rel="preload" as="image" href="/log/2017/toward-a-distributed-web/first-web-server.jpg"/><link rel="preload" as="image" href="/log/2017/toward-a-distributed-web/structure-http.svg"/><link rel="preload" as="image" href="/log/2017/toward-a-distributed-web/google-data-center.jpg"/><link rel="preload" as="image" href="/log/2017/toward-a-distributed-web/structure-bittorrent.svg"/><div class="note"><time class="opacity-50" dateTime="2023-01-01">Jan 1, 2023</time><p>Looking back at this write-up 4 years later, the problems and challenges described here feel as relevant as ever. Conversations about <em>solutions</em> on the other hand are a little less heartening, often dominated by “blockchains” or cryptocurrencies.</p><p>That said, I’d encourage readers to think beyond those narrow possibilities.
For me, my excitement and hopes for peer-to-peer tools, and alternative
internet structures remains strong.</p></div>
<figure><img src="/log/2017/toward-a-distributed-web/awh-snap.svg" alt="A folder making a face like x_x" width="1280" height="640"/></figure>
<p>Last week I discovered that <a href="https://ffffound.com" rel="nofollow noreferrer" target="_blank">ffffound</a> shut down.</p>
<p>I didn’t visit it often, but when I did, I was amazed by the variety of stuff on there. The site was an archive full of architectural photography, abstract art scans, and all kinds of other images. Seeing it gone made me a little sad.</p>
<p>Back in February, <a href="https://www.theverge.com/2016/10/28/13456208/why-vine-died-twitter-shutdown" rel="nofollow noreferrer" target="_blank">Vine</a> also shut down. Only a few months later, people shared similar <a href="https://techcrunch.com/2017/07/12/soundshroud/" rel="nofollow noreferrer" target="_blank">doubts about SoundCloud’s future</a>.</p>
<p>Often supported entirely by ad money, these community-companies on the web seem to struggle to stay online.</p>
<p>When conversations about internet shutdowns come up, people often ask, “what was their business model?” But, isn’t the web supposed to be an open, global community? Why is a business model a necessity for someone to make a meaningful site?</p>
<p>More and more, the web feels like a playground for businesses than <a href="https://en.wikipedia.org/wiki/Memex" rel="nofollow noreferrer" target="_blank">the information superhighway</a> it was originally conceived as.</p>
<h3 id="early-days"><a class="anchor" aria-hidden="true" href="#early-days"></a>Early Days</h3>
<figure><img src="/log/2017/toward-a-distributed-web/first-web-server.jpg" alt="The first web server, at CERN in Geneva" width="2048" height="1536"/></figure>
<p>The computer pictured above is the world’s first web server. Built to share documents amongst scientists at CERN, it was the first use of the HTTP protocol, the system that drives the internet. Colleagues of Tim Berners-Lee could access files on his computer from anywhere else in their local network.</p>
<p>On its casing is a sticky note warning: “This machine is a server. DO NOT POWER IT DOWN!” Makes sense. If the server is down, nobody can read the files on it.</p>
<figure><img src="/log/2017/toward-a-distributed-web/structure-http.svg" alt="A diagram showing many nodes &#x27;following&#x27; a central server." width="1280" height="683"/></figure>
<p>This little sticky note is a clue to a fundamental concept behind HTTP: it’s a request-response protocol. A “client” makes a request, and the “server” sends a response. One computer has a file, and many others ask for it. One server, many clients.</p>
<h3 id="these-days"><a class="anchor" aria-hidden="true" href="#these-days"></a>These Days</h3>
<p>The web has grown a lot since its early days. Instead of sticky notes on computers, internet infrastructure looks a little more like this:</p>
<figure><img src="/log/2017/toward-a-distributed-web/google-data-center.jpg" alt="Aisles and aisles of servers, lights blinking in the dark." width="2000" height="1333"/></figure>
<p>How we use it has changed too. We’re not just passing around text files, but images, videos, articles, and applications.</p>
<p>But the shape is still fundamentally the same. A domain name points to a single server. One server fields requests, responding to many clients.</p>
<p>The one-to-many shape leads to some interesting consequences.</p>
<p>For one, its enabled the rise of the “platform web.” Not everyone can or wants to run their own server, so people make platforms where others can contribute <em>without owning a server</em>: writing blog posts on Tumblr, sharing images on Flickr, putting life events on Facebook, etc. But running these platforms has costs, and over time, some platforms win out over others.</p>
<p>The platform web has become a marketplace. The only way to survive is to get as big as possible, hence our current world, where <a href="https://moz.com/top500" rel="nofollow noreferrer" target="_blank">a few platforms can account for the majority of all web traffic</a>.</p>
<p>And when one person or organization hosts all the code and all the data, the power dynamic changes.</p>
<ul>
<li>Facebook owns the server, so only they can see the code that decides what news to show you. To everyone else, its a black box</li>
<li>Google owns the servers holding people’s search history, making it easy to surveil without the knowledge or consent of the people putting that data there</li>
<li>Twitter owns the server, so Twitter gets paid to show you ads</li>
<li>The easy scalability of computation makes it possible for huge monopolies to form, resulting in more billionaire individuals than ever before in history.</li>
</ul>
<p>The assumptions of HTTP, expanded to a global scale, enable the centralization information, money, and power in an unprecedented way.</p>
<p>We trust these tech powerhouses to act in our best interest. Computers and networks will lead us to a more open, connected, and democratic future, right? But over and over again, we see <a href="https://www.are.na/jon-kyle-mohr/tech-ethnography" rel="nofollow noreferrer" target="_blank">ethically questionable decisions being made by these groups</a>. Over and over again, these groups aren’t held accountable for the power they wield.</p>
<h3 id="distributed-web"><a class="anchor" aria-hidden="true" href="#distributed-web"></a>Distributed Web</h3>
<p>For all that negativity, there’s tons of exciting work these days to find alternative shapes for the web. Shapes that benefit people, not corporations.</p>
<p>One exciting area is <em>distributed web technologies</em>, protocols vying to replace HTTP as the way we pass information across networks. There’s a few projects trying to fill this space. Some bigger ones: <a href="https://ipfs.io" rel="nofollow noreferrer" target="_blank">IPFS</a>, <a href="https://zeronet.io/" rel="nofollow noreferrer" target="_blank">Zeronet</a>, and <a href="http://datproject.org/" rel="nofollow noreferrer" target="_blank">Dat</a>.</p>
<figure><img src="/log/2017/toward-a-distributed-web/structure-bittorrent.svg" alt="An alternative structure diagram, where each node is connected to other nodes." width="1280" height="683"/></figure>
<p>Many of these alternatives share a resemblance with BitTorrent: one person starts to host something, and everybody re-hosts it as they view it. The distance between server and client collapses. No one person owns the servers. Instead, everyone is both a server and a client.</p>
<p>If the internet we’re moving towards today is the “corporate web”, distributed web tech tries to move towards a <a href="https://en.wikipedia.org/wiki/Cooperative" rel="nofollow noreferrer" target="_blank">cooperative</a> one: everyone works together to re-host things they’re interested in. Everyone’s responsible for keeping it online, sure, but everyone owns a piece of it.</p>
<p>What may seem like a simple change — a shift in how digital files move about — can have huge impact when applied at scale:</p>
<ul>
<li>How does a company make ad money if they can’t control what gets shown to people?</li>
<li>How do governments track and surveil people if the information was scattered across millions of computers, rather than neatly compiled by one organization in one place?</li>
<li>How do you monopolize (attention, data, etc) when the only way to grow is with the help of others?</li>
</ul>
<p>Distributed web protocols no longer give easy solutions to those challenges. The decisions behind decentralization make forming communities easy and forming companies hard.</p>
<p>All the alternatives I listed above are still in their early stages. There’s tons of challenges around usability, financing, and public interest/adoption. But for all the challenges, there’s some amazing opportunities too:</p>
<ul>
<li>What if a “like” wasn’t a gesture that told companies what ads to serve you, but instead <a href="https://twitter.com/taravancil/status/906585296648765440" rel="nofollow noreferrer" target="_blank">helped creators host their work</a>?</li>
<li>What if <a href="https://www.thewire.co.uk/in-writing/interviews/robert-barry-talks-to-mat-dryhurst-about-soundcloud" rel="nofollow noreferrer" target="_blank">the users of a platform could own and invest in its future</a>, rather than VC firms looking to make a profit?</li>
</ul>
<p>Anyways, the big point here is that we don’t need to continue to live out the consequences of technical decisions made 40+ years ago.</p>
<p>We have a responsibility as citizens of the web to challenge the politics embedded in our tools, the futures they’re leading us towards, and to question who they’re really serving.</p>
<h3 id="further-reading"><a class="anchor" aria-hidden="true" href="#further-reading"></a>Further Reading</h3>
<ul>
<li><a href="https://pfrazee.github.io/blog/what-is-the-p2p-web" rel="nofollow noreferrer" target="_blank">What is the P2P Web?</a></li>
<li><a href="https://beakerbrowser.com/" rel="nofollow noreferrer" target="_blank">Beaker Browser</a></li>
<li><a href="https://macwright.org/2017/07/20/decentralize-your-website.html" rel="nofollow noreferrer" target="_blank">So you want to decentralize your website</a></li>
</ul>]]></content:encoded>
        </item>
    </channel>
</rss>