{
  "version": "https://jsonfeed.org/version/1",
  "title": "offbyone.tech",
  "home_page_url": "https://offbyone.tech/",
  "feed_url": "https://offbyone.tech/feed.json",
  "description": "A mostly tech-related blog, maybe.",
  "author": {
    "name": "Zach Green",
    "url": "https://offbyone.tech/"
  },
  "items": [{
      "id": "https://offbyone.tech/posts/lighthouse-circleci/",
      "url": "https://offbyone.tech/posts/lighthouse-circleci/",
      "title": "Running Lighthouse in CircleCI",
      "content_html": "<h1>Running Lighthouse in CircleCI</h1>\n<h2>Update (October 2, 2018)</h2>\n<p>The code in this post uses <code>lighthouse</code> v2; it needs to be slightly modified to work with <code>lighthouse</code> v3. To skip straight to a working v3 example, visit the example repo I posted, which has been updated: https://github.com/zgreen/lighthouse-circleci-example.</p>\n<hr>\n<p>Recently I set up Lighthouse to run in CircleCI. At the time, I wasn't able to find many resources online about how to do this, so I'm posting my work here.</p>\n<h2>Writing the Lighthouse tests</h2>\n<p>Create a file for your tests. I'm not running a ton of tests at the moment, so I run them all in a single file, <code>lighthouse.test.js</code>.</p>\n<p>You'll need a pair of npm packages...</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> --save-dev lighthouse chrome-launcher</code></pre>\n<p>And something to execute your tests. This example uses Jest:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> --save-dev jest</code></pre>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> lighthouse <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"lighthouse\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Node CLI for Lighthouse https://www.npmjs.com/package/lighthouse#using-the-node-cli</span>\n<span class=\"token keyword\">const</span> chromeLauncher <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"chrome-launcher\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Launch Chrome from node</span></code></pre>\n<p>Jest sets a default timeout of 5 seconds for each test. Lighthouse can take longer to run, so you should increase that timeout if you're using Jest:</p>\n<pre class=\"language-js\"><code class=\"language-js\">jest<span class=\"token punctuation\">.</span><span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span><span class=\"token number\">60000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Give it a minute, just in case.</span></code></pre>\n<p>Next, you'll need a helper function for launching Chrome and running Lighthouse, and returning the audits:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token function-variable function\">launchChromeAndRunLighthouse</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>\n  <span class=\"token parameter\">url<span class=\"token punctuation\">,</span>\n  opts <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> <span class=\"token literal-property property\">chromeFlags</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  config <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span></span>\n<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n  chromeLauncher<span class=\"token punctuation\">.</span><span class=\"token function\">launch</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> <span class=\"token literal-property property\">chromeFlags</span><span class=\"token operator\">:</span> opts<span class=\"token punctuation\">.</span>chromeFlags <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">chrome</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    opts<span class=\"token punctuation\">.</span>port <span class=\"token operator\">=</span> chrome<span class=\"token punctuation\">.</span>port<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">lighthouse</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">,</span> opts<span class=\"token punctuation\">,</span> config<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">results</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n      chrome<span class=\"token punctuation\">.</span><span class=\"token function\">kill</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> results<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Finally, write a test! This example tests whether the first meaningful paint score for <code>https://example.com</code> is greater than or equal to <code>50</code>:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token function\">test</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Meaningful first paint score\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n  <span class=\"token function\">launchChromeAndRunLighthouse</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">https://example.com</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token punctuation\">{</span> audits <span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>audits<span class=\"token punctuation\">[</span><span class=\"token string\">\"first-meaningful-paint\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>score<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeGreaterThanOrEqual</span><span class=\"token punctuation\">(</span><span class=\"token number\">50</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Write as many of these as you want; Lighthouse returns a <em>ton</em> of useful data.</p>\n<h2>Add an npm <code>test</code> script</h2>\n<p>In <code>package.json</code>:</p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token property\">\"scripts\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"test\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"jest\"</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>In this example, I'm running tests in Jest, so... that's all that's required.</p>\n<h2>Execute these tests in CircleCI</h2>\n<p>This example assumes the use of CircleCI 2.0.</p>\n<p>Create a file, <code>.circleci/config.yml</code>:</p>\n<pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token comment\"># Javascript Node CircleCI 2.0 configuration file</span>\n<span class=\"token comment\">#</span>\n<span class=\"token comment\"># Check https://circleci.com/docs/2.0/language-javascript/ for more details</span>\n<span class=\"token comment\">#</span>\n<span class=\"token key atrule\">version</span><span class=\"token punctuation\">:</span> <span class=\"token number\">2</span>\n<span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">build</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">docker</span><span class=\"token punctuation\">:</span>\n      <span class=\"token comment\"># specify the version you desire here</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">image</span><span class=\"token punctuation\">:</span> circleci/node<span class=\"token punctuation\">:</span>9.8.0<span class=\"token punctuation\">-</span>browsers\n    <span class=\"token key atrule\">working_directory</span><span class=\"token punctuation\">:</span> ~/repo\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> checkout\n      <span class=\"token comment\"># Download and cache dependencies</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">restore_cache</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">keys</span><span class=\"token punctuation\">:</span>\n            <span class=\"token punctuation\">-</span> v1<span class=\"token punctuation\">-</span>dependencies<span class=\"token punctuation\">-</span>\n            <span class=\"token comment\"># fallback to using the latest cache if no exact match is found</span>\n            <span class=\"token punctuation\">-</span> v1<span class=\"token punctuation\">-</span>dependencies<span class=\"token punctuation\">-</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn install\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">save_cache</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">paths</span><span class=\"token punctuation\">:</span>\n            <span class=\"token punctuation\">-</span> node_modules\n          <span class=\"token key atrule\">key</span><span class=\"token punctuation\">:</span> v1<span class=\"token punctuation\">-</span>dependencies<span class=\"token punctuation\">-</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn test</code></pre>\n<p>You can use the <a href=\"https://circleci.com/docs/2.0/local-cli/\">CircleCI CLI</a> to run/test this out locally.</p>\n<h2>All done</h2>\n<p>That's it! Here's a repo with all of this put into action: https://github.com/zgreen/lighthouse-circleci-example</p>\n",
      "date_published": "2018-04-28T02:42:32Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/parsing-urls-from-text/",
      "url": "https://offbyone.tech/posts/parsing-urls-from-text/",
      "title": "Parsing URLs from text using a native web API",
      "content_html": "<h1>Parsing URLs from text using a native browser API</h1>\n<p>Here's one that that's a lot easier than it used to be.</p>\n<p>Say you're rendering a bunch of text, and that text may or may not contain one or more URLs. You'd like those URLs to be actual <code>&lt;a&gt;</code> tags when you render them, so that folks can click on them.</p>\n<p>It used to be, you'd have to do a little footwork, parsing the text for the protocol, pathname etc... an error prone process. Or you were smart about it and just relied on a npm module that handled all the edge cases and gave you a decent API.</p>\n<p>But, now you don't even need to write your own parsing logic nor do you need to add a dependency to handle it for you. You can use the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/URL\"><code>URL</code></a> browser API.</p>\n<p>If you're not familiar with it, <code>URL</code> is a really handy API, that takes in a URL and spits out a bunch of helpful properties:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>\n  <span class=\"token string\">\"https://offbyone.tech/parsing-urls-from-text-using-native-web-tech\"</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// URL {</span>\n<span class=\"token comment\">//   hash: \"\",</span>\n<span class=\"token comment\">//   host: \"offbyone.tech\"</span>\n<span class=\"token comment\">//   hostname: \"offbyone.tech\",</span>\n<span class=\"token comment\">//   href: \"https://offbyone.tech/parsing-urls-from-text-using-native-web-tech\",</span>\n<span class=\"token comment\">//   origin: \"https://offbyone.tech\",</span>\n<span class=\"token comment\">//   password: \"\",</span>\n<span class=\"token comment\">//   pathname: \"/parsing-urls-from-text-using-native-web-tech\",</span>\n<span class=\"token comment\">//   port: \"\",</span>\n<span class=\"token comment\">//   protocol: \"https:\",</span>\n<span class=\"token comment\">//   search: \"\",</span>\n<span class=\"token comment\">//   searchParams: URLSearchParams {  },</span>\n<span class=\"token comment\">//   username: \"\"</span>\n<span class=\"token comment\">// }</span></code></pre>\n<p>This replaces a bunch of stuff that we used to need custom code for, but it's also super handy for <em>parsing URLs</em>:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\nAut culpa sapiente autem quia enim. https://example.com Sequi consequatur nisi sapiente atque ut pariatur. Quibusdam veniam et repellendus ducimus eaque explicabo quia. Cumque quidem quaerat quia sit commodi voluptatem. Architecto libero et illum. Blanditiis https://duckduckgo.com modi quidem qui libero veniam in et. Amet autem fugiat nobis ex dolorem animi quidem. Non pariatur autem numquam et occaecati sapiente sit. Dolores hic fugit dolorem fuga voluptas. Possimus et cupiditate officiis voluptas. Voluptas qui minima sit sunt excepturi blanditiis dolore expedita.\n  </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> words <span class=\"token operator\">=</span> <span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">return</span> text<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">\" \"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">str<span class=\"token punctuation\">,</span> idx<span class=\"token punctuation\">,</span> arr</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> idx <span class=\"token operator\">+</span> <span class=\"token number\">1</span> <span class=\"token operator\">!==</span> arr<span class=\"token punctuation\">.</span>length <span class=\"token operator\">?</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>str<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> </span><span class=\"token template-punctuation string\">`</span></span> <span class=\"token operator\">:</span> str<span class=\"token punctuation\">;</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>str<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// If this doesn't throw an error, it's a URL!</span>\n        <span class=\"token keyword\">return</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">&lt;a href=\"</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>url<span class=\"token punctuation\">.</span>href<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">\"></span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>text<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">&lt;/a></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token comment\">// Error! It's not a URL, so we'll just proceed with it as a regular string</span>\n        <span class=\"token keyword\">return</span> text<span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>This works because <code>new URL()</code> just throws an error if it receives anything other than a URL.</p>\n<p>It's worth noting that that snippet won't quite work in React unless you use <code>dangerouslySetInnerHTML</code>. Here's a React-safe approach:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\nAut culpa sapiente autem quia enim. https://example.com Sequi consequatur nisi sapiente atque ut pariatur. Quibusdam veniam et repellendus ducimus eaque explicabo quia. Cumque quidem quaerat quia sit commodi voluptatem. Architecto libero et illum. Blanditiis https://duckduckgo.com modi quidem qui libero veniam in et. Amet autem fugiat nobis ex dolorem animi quidem. Non pariatur autem numquam et occaecati sapiente sit. Dolores hic fugit dolorem fuga voluptas. Possimus et cupiditate officiis voluptas. Voluptas qui minima sit sunt excepturi blanditiis dolore expedita.\n  </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token operator\">&lt;</span>blockquote<span class=\"token operator\">></span>\n      <span class=\"token punctuation\">{</span>text\n        <span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">\" \"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">reduce</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">acc<span class=\"token punctuation\">,</span> str<span class=\"token punctuation\">,</span> idx<span class=\"token punctuation\">,</span> arr</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> idx <span class=\"token operator\">+</span> <span class=\"token number\">1</span> <span class=\"token operator\">===</span> arr<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span>\n          <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>str<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// If this doesn't throw an error, it's a URL!</span>\n            acc<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>a href<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>url<span class=\"token punctuation\">.</span>href<span class=\"token punctuation\">}</span><span class=\"token operator\">></span><span class=\"token punctuation\">{</span>text<span class=\"token punctuation\">}</span><span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>a<span class=\"token operator\">></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n          <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token comment\">// Error! It's not a URL, so we'll just proceed with it as a regular string</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span> acc<span class=\"token punctuation\">[</span>acc<span class=\"token punctuation\">.</span>length <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">===</span> <span class=\"token string\">\"string\"</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n              acc<span class=\"token punctuation\">[</span>acc<span class=\"token punctuation\">.</span>length <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> acc<span class=\"token punctuation\">[</span>acc<span class=\"token punctuation\">.</span>length <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">+</span> text<span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n              acc<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n          <span class=\"token punctuation\">}</span>\n          <span class=\"token keyword\">return</span> acc<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">item</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> item<span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>blockquote<span class=\"token operator\">></span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre>\n<p>That's it! That's all you have to do. Browser support is very good, but doesn't include IE. If you need to support older browsers, <a href=\"https://polyfill.io/v3/\">polyfill.io</a> can cover this one:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span>\n  <span class=\"token attr-name\">crossorigin</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>anonymous<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://polyfill.io/v3/polyfill.min.js?flags=gated&amp;features=URL<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span><span class=\"token script\"></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span></code></pre>\n",
      "date_published": "2019-10-05T18:59:20Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/11ty-cloudflare-workers-github-actions/",
      "url": "https://offbyone.tech/posts/11ty-cloudflare-workers-github-actions/",
      "title": "Automatic static site deployment with 11ty, Cloudflare Workers, and GitHub actions",
      "content_html": "<h2>Update (January 3, 2024)</h2>\n<p>This is all way easier now. <a href=\"https://developers.cloudflare.com/pages/framework-guides/deploy-an-eleventy-site/\">Here are the docs</a>.</p>\n<hr>\n<h1>Automatic static site deployment with 11ty, Cloudflare Workers, and GitHub actions</h1>\n<p>Time for my one blog post of the year! Approriately, it concerns my having completely reimplemented this blog's tech stack and workflow for the umpteenth time. This has become a more or less annual ritual. And, like many rituals, it is arguably totally pointless! Nevertheless!</p>\n<p>Having said that, I've achieved something with this workflow that I've been seeking more or less since I started this blog: a <em>simple</em> workflow wherein I write one blog post in Markdown, push it to my GitHub repo, and the whole site is then automatically rebuilt and deployed. This blog post will be the inaugural test run.</p>\n<p>What follows is a brief meta-tutorial explaining my workflow, and linking to external guides that I used to get set up. If you're completely unfamiliar with everything in the title of this post, you might still be able to get an idea of how it all fits together by reading through this. That said, this post will probably be most useful to folks that know how to use <a href=\"https://www.npmjs.com/\">npm</a> and that have a passing familiarity with some of the tech in the post title.</p>\n<h2>Requirements</h2>\n<ul>\n<li><a href=\"https://www.11ty.dev/\">11ty</a> (open source, free)</li>\n<li><a href=\"https://workers.cloudflare.com/\">Cloudflare Workers account</a> (free account available)</li>\n<li><a href=\"https://github.com/\">GitHub account</a> (free account available)</li>\n</ul>\n<h3>11ty</h3>\n<p>11ty is static-site generator. If you're totally unfamiliar with what that is, it basically means you can author your pages in <a href=\"https://en.wikipedia.org/wiki/Markdown\">Markdown</a> and build a static HTML version of those pages. If you're already familiar, then 11ty is basically a lot like other static site generators, except that it's a bit simpler than some of the most popular solutions (like <a href=\"https://nextjs.org/\">Next.js</a>, which this blog used to use, or <a href=\"https://www.gatsbyjs.com/\">Gatsby</a>, which this blog <em>also</em> used to use...) currently out there. <a href=\"https://www.11ty.dev/docs/\">Get started with it here.</a></p>\n<h3>Cloudflare Workers</h3>\n<p>This is what I'm using to host the static assets (in my case, just plain old HTML). <a href=\"https://workers.cloudflare.com/\">Cloudflare Workers</a> is a serverless execution environment that runs on Cloudflare's network. The specific workers platform is <a href=\"https://developers.cloudflare.com/workers/platform/sites\">Workers Sites</a>, which is built especially for deploying static sites.</p>\n<h3>GitHub actions</h3>\n<p><a href=\"https://github.com/features/actions\">GitHub Actions</a> is a CI/CD platform; it lets you automate all kinds of things. In my case, I use it to automate building the blog, and then publishing it to Cloudflare.</p>\n<h2>Step 1: Set up the blog</h2>\n<p>Using 11ty, this just means following the <a href=\"https://www.11ty.dev/docs/getting-started/\">getting started docs</a>. But 11ty isn't a requirement; you could use any static site generator, or even just author the HTML by hand (really!).</p>\n<h2>Step 2: Set up Cloudflare Workers</h2>\n<p>I'm using a modified version of Cloudflare's <a href=\"https://developers.cloudflare.com/workers/platform/sites\">Workers Sites</a>, which runs on Cloudflare workers. You can either <a href=\"https://developers.cloudflare.com/workers/platform/sites/start-from-existing\">follow these docs to start from an existing static site</a>, or <a href=\"https://developers.cloudflare.com/workers/platform/sites/start-from-scratch\">these to start from scratch</a>.</p>\n<h2>That's it, if you like.</h2>\n<p>You don't need to set up GitHub actions or even use GitHub at all if you want to publish a static site to Cloudflare Workers. <a href=\"https://developers.cloudflare.com/workers/platform/sites/start-from-existing\">As noted in the docs</a>, you can simply run <code>wrangler publish</code> once you're all set up, and your site will be published to a publically viewable URL.</p>\n<h2>GitHub actions</h2>\n<p>The advantage of using a GitHub action is that I can push a new blog post or page to my GitHub repo for this site, and the site will be rebuilt and published automatically. It's just a workflow improvement.</p>\n<p>To set up the action, I added this file to <code>.github/workflows/deploy.yml</code>:</p>\n<pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> deploy\n\n<span class=\"token key atrule\">on</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">push</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">branches</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token string\">\"master\"</span>\n\n<span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">deploy</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n    <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> deploy\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v2\n\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> npm ci\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> npm run build\n\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Publish\n        <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> cloudflare/wrangler<span class=\"token punctuation\">-</span>action@1.3.0\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">apiToken</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> secrets.CF_API_TOKEN <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n        <span class=\"token key atrule\">env</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">CF_ACCOUNT_ID</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> secrets.CF_API_TOKEN <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n          <span class=\"token key atrule\">USER</span><span class=\"token punctuation\">:</span> root</code></pre>\n<p>This Yaml conforms to the required syntax for GitHub actions; it's taken from <a href=\"https://github.com/cloudflare/wrangler-action\">here</a>. This action installs the necessary dependencies from <code>npm</code>, builds the site, and publishes it.</p>\n<h3>Secrets</h3>\n<p>You can use GitHub secrets to store encrypted environment variables. The advantage of using secrets in this case is that I can store <code>CF_API_TOKEN</code> without writing it directly into the <code>wrangler.toml</code> file, where it might be unintentionally exposed.</p>\n<p>You can set secrets for a GitHub repository by visiting <code>Settings &gt; Secrets</code> in the repo's navigation.</p>\n<p>You might note that the Yaml snippet is slightly different than <a href=\"https://github.com/cloudflare/wrangler-action#usage\">the example given in the action's GitHub README</a>. This is because I chose not to include my Cloudflare workers account id in the site's <code>wrangler.toml</code> file. <a href=\"https://github.com/cloudflare/wrangler-action/issues/17\">According to this issue</a> it's safe to do so, but you can also use GitHub secrets and a Cloudflare workers API token (with appropriate permissions) to do the same thing without potentially exposing your account ID.</p>\n<h2>It works!</h2>\n<p>With this action in place, your site will automatically be rebuilt and published to Cloudflare on any pushes to the <code>master</code> branch. You can modify that branch name to be something else, and/or add additional branch names.</p>\n<p>This should be all you need to get going; read on for some additional tips and tricks.</p>\n<h2>Further notes</h2>\n<h3>Skipping step #2</h3>\n<p>You can actually skip the step of manually setting up a Cloudflare Workers site, and use the GitHub action all by itself to do this. <a href=\"https://github.com/zgreen/tomato\">See this repo</a> for an example. You'll note that repository has no <code>workers-site/index.js</code> file at all; it's handled by the Github Action directly.</p>\n<h3>Customizing the workers site behavior</h3>\n<p>You may, like me, need to customize some aspect of your static site at the Cloudflare worker level. This is the main reason for setting up a Workers Site as outlined in step #2.</p>\n<h4>Redirects</h4>\n<p>I had some <a href=\"https://nextjs.org/docs/api-reference/next.config.js/redirects\">redirects set up with Next.js</a>, and 11ty, being a much more basic tool, has no way to pull this off. Fortunately, it's quite easy to do in Cloudflare Workers. Here's my entire <code>workers/index.js</code> file, for reference:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  getAssetFromKV<span class=\"token punctuation\">,</span>\n  mapRequestToAsset<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@cloudflare/kv-asset-handler\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token constant\">DEBUG</span> <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"fetch\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    event<span class=\"token punctuation\">.</span><span class=\"token function\">respondWith</span><span class=\"token punctuation\">(</span><span class=\"token function\">handleEvent</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">DEBUG</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">return</span> event<span class=\"token punctuation\">.</span><span class=\"token function\">respondWith</span><span class=\"token punctuation\">(</span>\n        <span class=\"token keyword\">new</span> <span class=\"token class-name\">Response</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">.</span>message <span class=\"token operator\">||</span> e<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token literal-property property\">status</span><span class=\"token operator\">:</span> <span class=\"token number\">500</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    event<span class=\"token punctuation\">.</span><span class=\"token function\">respondWith</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token class-name\">Response</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Internal Error\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> <span class=\"token literal-property property\">status</span><span class=\"token operator\">:</span> <span class=\"token number\">500</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">handleEvent</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">.</span>request<span class=\"token punctuation\">.</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> pathname<span class=\"token punctuation\">,</span> search<span class=\"token punctuation\">,</span> hash<span class=\"token punctuation\">,</span> origin <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> url<span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// redirects</span>\n  <span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span>pathname<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">\"/json\"</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> Response<span class=\"token punctuation\">.</span><span class=\"token function\">redirect</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>origin<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/feed.json</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> <span class=\"token number\">301</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">\"/lighthouse-circleci\"</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> Response<span class=\"token punctuation\">.</span><span class=\"token function\">redirect</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>origin<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/posts/lighthouse-circleci</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> <span class=\"token number\">301</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">\"/posts/parsing-urls-from-text-using-native-web-tech\"</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> Response<span class=\"token punctuation\">.</span><span class=\"token function\">redirect</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>origin<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/posts/parsing-urls-from-text</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> <span class=\"token number\">301</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> <span class=\"token string\">\"/rss\"</span><span class=\"token operator\">:</span>\n      <span class=\"token keyword\">return</span> Response<span class=\"token punctuation\">.</span><span class=\"token function\">redirect</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>origin<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/feed.xml</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> <span class=\"token number\">301</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// no default</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">let</span> options <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">/**\n   * You can add custom logic to how we fetch your assets\n   * by configuring the function `mapRequestToAsset`\n   */</span>\n  <span class=\"token comment\">// options.mapRequestToAsset = handlePrefix(/^\\/docs/)</span>\n\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">DEBUG</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token comment\">// customize caching</span>\n      options<span class=\"token punctuation\">.</span>cacheControl <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">bypassCache</span><span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> <span class=\"token function\">getAssetFromKV</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">,</span> options<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// if an error is thrown try to serve the asset at 404.html</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span><span class=\"token constant\">DEBUG</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">let</span> notFoundResponse <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">getAssetFromKV</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token function-variable function\">mapRequestToAsset</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n            <span class=\"token keyword\">new</span> <span class=\"token class-name\">Request</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">.</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>origin<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/404/index.html</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span> req<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Response</span><span class=\"token punctuation\">(</span>notFoundResponse<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token operator\">...</span>notFoundResponse<span class=\"token punctuation\">,</span>\n          <span class=\"token literal-property property\">status</span><span class=\"token operator\">:</span> <span class=\"token number\">404</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Response</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">.</span>message <span class=\"token operator\">||</span> e<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> <span class=\"token literal-property property\">status</span><span class=\"token operator\">:</span> <span class=\"token number\">500</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>You'll note the <code>switch</code> block in the <code>handleEvent</code> function. That's what handles the redirects.</p>\n<h4>Feeds</h4>\n<p>My Next.js-powered site had <a href=\"https://jsonfeed.org/\">json</a> and RSS feeds, generated dynamically in Node. But there's no reason these feeds can't also be generated statically along with the rest of the site. Doing so is easy enough using <a href=\"https://github.com/11ty/eleventy-plugin-rss\">@11ty/eleventy-plugin-rss</a>.</p>\n<h3>Custom domain</h3>\n<p>During testing, I was deploying the site to my <a href=\"https://blog.cloudflare.com/announcing-workers-dev/\">workers.dev subdomain</a>. I set up my custom domain using Cloudflare's DNS management (the actual domain I purchased elsewhere). I had to add this line to my <code>wrangler.toml</code>:</p>\n<pre class=\"language-toml\"><code class=\"language-toml\"><span class=\"token key property\">route</span> <span class=\"token punctuation\">=</span> <span class=\"token string\">\"offbyone.tech/*\"</span></code></pre>\n<h3>Next.js -&gt; 11ty</h3>\n<p>I still really like <a href=\"https://nextjs.org/\">Next.js</a>, and it remains my go-to framework for any project that needs such a thing. But for running a blog, it wasn't great. <a href=\"https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript\">There is an example in their repo</a>, but managing things simple things like post metadata, feeds, and having a list of all posts on the blog home page always felt harder to me than it needed to be. 11ty is just simpler; it has fewer moving parts, and it does its primary task—building a static site—quickly and easily. The major trade off is that by switching to a completely static build, I lose the server-side Node.js environment I had with Next.js. (Next.js <em>does</em> have a really good <a href=\"https://nextjs.org/docs/advanced-features/static-html-export\">static export feature</a>, but I ran the blog with server-side rendering in Node.) Fortunately, the use of Cloudflare Workers seems to more than make up for this loss; I can do everything in a worker I was already using the Node server for, plus, it seems, <a href=\"https://developers.cloudflare.com/workers/examples\">quite a bit more</a>.</p>\n<h3>Vercel -&gt; Cloudflare</h3>\n<p>For a while, this blog was hosted on <a href=\"https://vercel.com\">Vercel</a>. I like Vercel, and have been using the platform since their v1. But the platform has had <em>a lot</em> of churn in a really short period of time. Their first version was defined by a really nice CLI and the ability to deploy pretty much any kind of application using a Docker container. The second version moved to an entirely serverless model, which still suited my needs well. But along the way the CLI decayed, to the point where it was not as fun to use. There were also a ton of pricing changes, and I could never really sort what size bill I might receive if the blog were suddenly to get a lot of traffic. Cloudflare, I'm hoping, will be a bit more stable. It's also cheaper (I'm paying for a $5/month Cloudflare Workers account) and the site now loads <em>a lot</em> faster.</p>\n<p>That's it. Thanks for reading!</p>\n",
      "date_published": "2020-12-23T11:54:50Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/simpler-jest-mocking/",
      "url": "https://offbyone.tech/posts/simpler-jest-mocking/",
      "title": "Simpler Jest mocking",
      "content_html": "<h1>Simpler Jest mocking</h1>\n<p>There are <a href=\"https://jestjs.io/docs/en/manual-mocks#mocking-user-modules\">a lot</a> <a href=\"https://jestjs.io/docs/en/manual-mocks#mocking-node-modules\">of ways</a> <a href=\"https://jestjs.io/docs/en/manual-mocks#using-with-es-module-imports\">to mock</a> <a href=\"https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock\">things</a> <a href=\"https://jestjs.io/docs/en/jest-object#jestfnimplementation\">in <code>jest</code></a>. And that's not counting spies, <a href=\"https://jestjs.io/docs/en/es6-class-mocks#keeping-track-of-usage-spying-on-the-mock\">which are like mocks</a>, <a href=\"https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname\">but different</a>.</p>\n<p>I still probably haven't read all the mocking-related Jest docs. Why would I do that? Anyway, the docs describe the preferred way to mock, and it's super simple:</p>\n<blockquote>\n<p>If the module you are mocking is a Node module (e.g.: lodash), the mock should be placed in the <strong>mocks</strong> directory adjacent to node_modules</p>\n</blockquote>\n<p>Nice. I've used this feature a lot. <a href=\"https://www.npmjs.com/package/jest-mock-axios\">For stuff like this</a>:</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token comment\">// ./__mocks__/axios.js</span>\n<span class=\"token keyword\">import</span> mockAxios <span class=\"token keyword\">from</span> <span class=\"token string\">\"jest-mock-axios\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> mockAxios<span class=\"token punctuation\">;</span></code></pre>\n<p>Now when I <code>import axios from 'axios'</code> in a test file... I'm not actually importing it. I'm importing the mock.</p>\n<p>On the one hand, this is really nice. I can write one mock file, and now anytime I'm in a test environment, <code>axios</code> is automatically mocked for me, and <a href=\"https://www.npmjs.com/package/jest-mock-axios#axios-mock-api\">I've got some custom test APIs I can run</a>. 🆒</p>\n<p>But it turns out this is kind of a pain in the ass sometimes! Specifically:</p>\n<ol>\n<li>If you <em>don't</em> want to mock the module, or</li>\n<li>if you want to mock the module differently, or</li>\n<li>you forgot that you mocked the module in the first place.</li>\n</ol>\n<p>The thing that sucks about this is that, in the context of your text file, the mock is implicit; there's no indication that module is mocked. When you look at the file for the first time, or come back to it after not having looked at it in a while, and see this:</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token comment\">// my.test.tsx</span>\n<span class=\"token keyword\">import</span> axios <span class=\"token keyword\">from</span> <span class=\"token string\">\"axios\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>...you're probably going to assume that that file has imported the <code>axios</code> module. But it hasn't!</p>\n<p>This can get even worse if you're mocking your own modules, where the docs recommend this:</p>\n<blockquote>\n<p>Manual mocks are defined by writing a module in a <strong>mocks</strong>/ subdirectory immediately adjacent to the module. For example, to mock a module called user in the models directory, create a file called user.js and put it in the models/<strong>mocks</strong> directory. Note that the <strong>mocks</strong> folder is case-sensitive, so naming the directory <strong>MOCKS</strong> will break on some systems.</p>\n</blockquote>\n<p>Do this an you'll end up with a bunch of <code>__mocks__</code> directories all over your codebase.</p>\n<p>You may, as I have, come to regret this choice, especially if you've already written a lot of tests. Read the docs. There's a simpler way:</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token comment\">// my.test.ts</span>\n<span class=\"token keyword\">import</span> axios <span class=\"token keyword\">from</span> <span class=\"token string\">\"axios\"</span><span class=\"token punctuation\">;</span>\n\njest<span class=\"token punctuation\">.</span><span class=\"token function\">mock</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"axios\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">test</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"example test with a mock\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>axios<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toHaveBeenCalled</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>The nice thing about this is that the mock is explicit! It says <code>.mock</code> right there in the file! And that's all you need to do. No magic. Mocks are inherently weird and confusing—avoid them if you can—because the code you <code>import</code> is not really the code that gets tested. Sort of!</p>\n<p>Keep it simple, that's all.</p>\n<p>Credit to <a href=\"https://dev.to/zaklaughton/the-only-3-steps-you-need-to-mock-an-api-call-in-jest-39mb\">this post</a>, which set me straight a little bit.</p>\n",
      "date_published": "2020-12-29T18:21:00Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/secret-urls-with-11ty/",
      "url": "https://offbyone.tech/posts/secret-urls-with-11ty/",
      "title": "Secret URLs with 11ty",
      "content_html": "<h1>Secret URLs with 11ty</h1>\n<p>I sometimes want to be able to create secret pages on this site; I want a unique URL that I can share with someone, but I basically don't want anyone to be able to find the page unless I've shared the URL with them. <a href=\"https://docs.github.com/en/github/writing-on-github/creating-gists\">This is basically how GitHub secret gists work</a>:</p>\n<blockquote>\n<p>Secret gists aren't private. If you send the URL of a secret gist to a friend, they'll be able to see it. However, if someone you don't know discovers the URL, they'll also be able to see your gist.</p>\n</blockquote>\n<p>It's quite simple to do with 11ty. <a href=\"/secrets/ALtX3rwMv5EXx8x4vUGnA\">Here's a working example</a>, and here's how I built it:</p>\n<ol>\n<li>Create a new subdirectory in your site folder. Mine is <code>site/secrets/</code>.</li>\n<li>Generate a unique, URL-safe ID. <a href=\"https://github.com/ai/nanoid/\">Nano ID</a> is great for this. You can just run <code>npx nanoid</code> if you have Node.js installed.</li>\n<li>Create a markdown file, with this ID as the file name, inside your <code>secrets</code> directory. The example file I'm using here is: <code>site/secrets/ALtX3rwMv5EXx8x4vUGnA.md</code>.</li>\n<li>Add some metadata and text. You can put whatever you like here—the only important thing is that you add a specific <a href=\"https://www.11ty.dev/docs/collections/\">tag</a>. On this site, I just use <code>tags: [&quot;secret&quot;]</code>.</li>\n<li>On the <a href=\"https://www.11ty.dev/docs/layouts/\">11ty layout template</a> for your page, add the following snippet your HTML <code>&lt;head&gt;</code>:</li>\n</ol>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>\n  tags <span class=\"token operator\">&amp;&amp;</span> tags<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"secret\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token operator\">?</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">&lt;meta name=\"robots\" content=\"noindex, nofollow\" /></span><span class=\"token template-punctuation string\">`</span></span>\n    <span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span>\n<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">\n</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span></code></pre>\n<p>That ensures the page won't be indexed by search engines, and that search engines won't follow links on the page.</p>\n",
      "date_published": "2021-04-09T14:29:07Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/useful-commands-from-my-shell-history/",
      "url": "https://offbyone.tech/posts/useful-commands-from-my-shell-history/",
      "title": "Useful Commands From My Shell History",
      "content_html": "<h1>Useful commands from my shell history</h1>\n<p>My shell history is pretty boring, mostly a lot of <code>npm</code> and <code>git</code>, but there are a handful specific commands I need infrequently, and that I always pull up by <code>Ctrl+R</code>-ing through my shell because the commands themselves are too long and weird to commit to memory.</p>\n<p>I'm documenting those here, should my shell history ever be lost.</p>\n<h2>Convert a web extension to a Safari web extension:</h2>\n<p>Safari &quot;supports&quot; web extensions, except you still have to convert them and build them with Xcode. I don't quite know what the logic is for when this command has to be run/rerun—it seems to be whenever a new file is <em>added</em>. Changed files get picked up by Xcode, I think? I really don't like building web extensions for Safari.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\">xcrun safari-web-extension-converter web-extension/path --project-location web-extension/safari --macos-only</code></pre>\n<h2>Convert an .mp4 video to a .webm video:</h2>\n<pre class=\"language-bash\"><code class=\"language-bash\">ffmpeg <span class=\"token parameter variable\">-i</span> VIDEO_NAME.mp4 <span class=\"token parameter variable\">-c:v</span> libvpx-vp9 <span class=\"token parameter variable\">-pix_fmt</span> yuva420p VIDEO_NAME.webm</code></pre>\n<h2>Sync a specific file type to AWS S3, with multi-factor authentication enabled:</h2>\n<p>For example, this command will sync only <code>.json</code> files:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\">aws s3 <span class=\"token function\">sync</span> <span class=\"token builtin class-name\">.</span> s3://your/directory/path <span class=\"token parameter variable\">--exclude</span> <span class=\"token string\">\"*.*\"</span>  <span class=\"token parameter variable\">--include</span> <span class=\"token string\">\"*.json\"</span> <span class=\"token parameter variable\">--profile</span> mfa <span class=\"token parameter variable\">--dryrun</span></code></pre>\n<p>Note: that command has the <code>--dryrun</code> flag. It won't actually do anything—it will just tell you what it <em>will</em> do without flag. Remove <code>--dryrun</code> to do it for real.</p>\n<h2>AWS multi-factor auth:</h2>\n<p><a href=\"https://repost.aws/knowledge-center/authenticate-mfa-cli\">Related docs</a></p>\n<pre class=\"language-bash\"><code class=\"language-bash\">aws sts get-session-token --serial-number SERIAL_NUMBER_HERE --token-code TOKEN_CODE_HERE</code></pre>\n",
      "date_published": "2023-09-29T13:06:33Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/the-search-term-you-want-is-share-target/",
      "url": "https://offbyone.tech/posts/the-search-term-you-want-is-share-target/",
      "title": "The search term you want is: `share_target`",
      "content_html": "<h1>The search term you want is: <code>share_target</code></h1>\n<p>Sometimes you don't know the name of the thing you're looking for; sometimes you have a question, but don't know how to ask it. And only when you discover the exact search term does that unlock the information you've been trying to find.</p>\n<p>Here's an example. On macOS, lots of applications offer this native share menu. Here's a screenshot from Safari:</p>\n<p><img src=\"/img/macos-share-menu-example.png\" alt=\"Screenshot of a macOS share menu\"></p>\n<p>Initially I wasn't sure if &quot;share menu&quot; was the right term for that menu, <a href=\"https://support.apple.com/guide/mac-help/use-the-share-menu-on-mac-mh40614/mac\">but it looks like it is</a>.</p>\n<p>But what I was wondering was: was it possible to add a <em>webapp</em> to that menu? For example, if I have a webapp that can receive images (like <a href=\"https://squoosh.app/\">Squoosh</a>), could I &quot;add&quot; that webapp as an option in the macOS share menu? It seems like I should be able to.</p>\n<p>I searched for things like &quot;add webapp to share menu macos&quot;, &quot;add item to share menu macos&quot;, and other similar permutations. This generally just yielded posts with instructions on how to modify the share menu items as an end user—i.e. how to add or remove specific native applications. But there was nothing I could find about programatically adding a webapp to that menu.</p>\n<p>I also tried searches that included <code>pwa</code> and <code>progressive web app</code>, since it seemed possible that this functionality might be tied to &quot;installing&quot; the webapp somehow. No luck. Every few months I'd search around, find nothing useful, and then give up.</p>\n<p>But then one day, I found the term I wanted. I actually don't remember <em>how</em> I found it. But the term you want is <code>share_target</code>! <a href=\"https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target\">Here's the MDN link.</a> (It's entirely possible I was just surfing around MDN and then stumbled across this—I've accidentally learned a number of things that way.)</p>\n<p>Here are a few other helpful links:</p>\n<ul>\n<li><a href=\"https://developer.chrome.com/docs/capabilities/web-apis/web-share-target\">https://developer.chrome.com/docs/capabilities/web-apis/web-share-target</a></li>\n<li><a href=\"https://web.dev/patterns/files/receive-shared-files\">https://web.dev/patterns/files/receive-shared-files</a></li>\n<li><a href=\"https://w3c.github.io/web-share-target/\">https://w3c.github.io/web-share-target/</a></li>\n<li><a href=\"https://github.com/WebKit/standards-positions/issues/11\">https://github.com/WebKit/standards-positions/issues/11</a></li>\n</ul>\n<p>It turns out the exact functionality I wanted has already been spec'd out! Great!</p>\n<p>Unfortunately it seems not to be supported by Apple. I actually can't find any documentation that <em>officially</em> says it is or isn't supported... but adding <code>share_target</code> to a <code>manifest.json</code> doesn't have any effect. The closest documentation I could find was <a href=\"https://github.com/WebKit/standards-positions/issues/11\">this WebKit GitHub issue</a>, which suggests that the WebKit team is relatively neutral on the issue, but also suggests that they haven't actually implemented it.</p>\n<p>Oh well. At least now I know what to search for. The official macOS way to add items to the native share menu seems to be add a &quot;Share Extension&quot; as a new &quot;target&quot; in Xcode:</p>\n<p><img src=\"/img/xcode-share-extension.png\" alt=\"Screenshot the &quot;Share Extension&quot; target option in Xcode\"></p>\n<p>Documentation about this is also pretty scant.</p>\n",
      "date_published": "2024-01-23T13:51:07Z"
    }
    ,{
      "id": "https://offbyone.tech/posts/the-search-term-you-want-is-time-stretching/",
      "url": "https://offbyone.tech/posts/the-search-term-you-want-is-time-stretching/",
      "title": "The search term you want is: `time stretching`",
      "content_html": "<h1>The search term you want is: <code>time stretching</code></h1>\n<p>Here's another case where <a href=\"/post/the-search-term-you-want-is-share-target\">discovering the right search term unlocks a wealth of information</a>.</p>\n<p>I've used the Web Audio API for a number of different projects at this point, and I'm pretty familiar with it. But a couple years ago I discovered something surprising: adjusting the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/playbackRate\">playback rate of an <code>AudioBufferSourceNode</code></a> will also change the pitch of the audio.</p>\n<p>This was surprising, because when you change the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate\">playback rate of an <code>HTMLMediaElement</code></a> (e.g. an <code>&lt;audio&gt;</code> or <code>&lt;video&gt;</code> element), the pitch of the audio does not change.</p>\n<p>I think it's safe to say that most folks that work with audio on the web only ever need to use the <code>&lt;audio&gt;</code> tag. Most of the time, you have an audio src URL, like <code>https://example.com/my.mp3</code>, and you just need to play it. The <code>&lt;audio&gt;</code> tag works great for this simple, common use case. The <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API\">Web Audio API</a> is much lower level. You might use it if you wanted to add sound effects to game, build a synthesizer in the browser, or maybe build a custom audio player with some very specific requirements.</p>\n<p>It's also pretty common that a user might want to adjust the playback rate of the audio. If they want to get through audio a bit faster, they'll speed it up, and if they need more time to process it (for example, if it's in a foreign language, or the speakers are talking unusually fast), they'll slow it down. And if you're working with an <code>&lt;audio&gt;</code> tag, all you have to do is adjust the <code>playback</code> rate; the audio will speed up or slow down, but the user will notice no change in pitch.</p>\n<p>Not so with the Web Audio API—a playback rate above 1 will give you a chipmunk voice, and playback rate below 1 will give you a drunk voice <a href=\"https://www.youtube.com/watch?v=5uC8mRy2p9w\">(reliably hilarious)</a>.</p>\n<p>What I didn't realize—what I never had to think about—was that the <code>&lt;audio&gt;</code> tag was actually doing some extra work for me whenever I adjusted the playback rate. Think about it: if you play an analog audio source, like tape or vinyl, faster or slower than expected, you get the chipmunk/drunk effect. You can also think of it as an actual wave: increasing the speed of wave means shortening the wavelength (and thus a higher pitch). So why doesn't that happen with <code>&lt;audio&gt;</code>? Well, it's right there in the MDN docs:</p>\n<blockquote>\n<p>The pitch of the audio is corrected by default. You can disable pitch correction using the <code>HTMLMediaElement.preservesPitch</code> property.</p>\n</blockquote>\n<p>Ah. Does <code>AudioBufferSourceNode</code> have this property, or something like it?</p>\n<p><a href=\"https://github.com/WebAudio/web-audio-api/issues/2487\">No.</a> I've been subscribed to that issue for about two years now. My guess is we'll get something <em>eventually</em>, but I'm not holding my breath.</p>\n<p>Searching in vain for a simple, native solution, I tried variations of &quot;web audio change playback rate without changing pitch&quot;. <a href=\"https://stackoverflow.com/questions/31274895/changing-speed-of-audio-using-the-web-audio-api-without-changing-pitch\">Here's a Stack Overflow question that asks the very same thing</a> (note that the answers are not very helpful).</p>\n<p>But the term you want is <strong>time stretching</strong>. <a href=\"https://en.wikipedia.org/wiki/Audio_time_stretching_and_pitch_scaling\">Here's Wikipedia:</a></p>\n<blockquote>\n<p>Time stretching is the process of changing the speed or duration of an audio signal without affecting its pitch.</p>\n</blockquote>\n<p>You can <a href=\"https://www.npmjs.com/search?q=time%20stretching\">search this on npm to get started</a>, but I should warn you: there really is no easy way to do this. There's no native solution, and no library I've ever found that implements it in a simple and straightforward way (probably because it's neither a simple nor straightforward problem).</p>\n",
      "date_published": "2024-01-30T13:27:58Z"
    }
    
  ]
}
