<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[A Tech Blog]]></title><description><![CDATA[Generally creative person.]]></description><link>https://blog.aidanbreen.com/</link><generator>RSS for Node</generator><lastBuildDate>Sun, 05 Apr 2020 14:25:41 GMT</lastBuildDate><item><title><![CDATA[Table.Fish - Going Viral.]]></title><description><![CDATA[Of all the weird domain names I have bought over the years,  Table.Fish  is an unlikely late bloomer. Originally purchased to be the home of…]]></description><link>https://blog.aidanbreen.com//table-fish-1/</link><guid isPermaLink="false">https://blog.aidanbreen.com//table-fish-1/</guid><pubDate>Sun, 05 Apr 2020 12:46:37 GMT</pubDate><content:encoded>&lt;p&gt;Of all the weird domain names I have bought over the years, &lt;a href=&quot;https://www.table.fish&quot;&gt;Table.Fish&lt;/a&gt; is an unlikely late bloomer. Originally purchased to be the home of a restaurant table booking app, it was quickly shelved and lay dormant for over a year. It’s now home to my newest passion project: a virtual table quiz platform.&lt;/p&gt;
&lt;p&gt;It’s a simple concept, the quizmaster hosts a live stream on youtube with the questions. Players can submit their answers through our app.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/796fe17faa75efea219b33ebac546cd3/06def/screens.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 52.68817204301075%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACmUlEQVQoz3WSX0hTURzH7+6Gc4l/FkimZlgU1IMJEvTgKNCgpAWBaBhJET0ERfkQ9JREVBKlNOkhJ2QZFcnm2so0t927uevd3dRURJdJOhVFM1yzqWX67dxzSaTowpdzfr8f53PP7/s7TMCai2Uvg7l2DSJulqxqLPAsYl4VYp7/aL3GYomc/e6Nx6xwCBHPFjDoYrDYwVJIlGMx71IDAQboIeraoO4N2hhLDFYlLb52X8SytFsBTrzVovdFAl1HbDqEHVoMW3UkjsOoXYvxN1p8ao7HR4uOatwRR2IdRkiOQj/IP1FhVVQRIEmELJsgPE5CoDERnFmP4eYEEuvB1+shNqSgs0FPFXyWTOqbyT4Z0tMk9DxPxNhrDUJNLGbb1FjzMwRI2vshqLDsk71hiY+kZVJYExjq7YqP1GWf3KQ1Qdn/9Cn58Pu98LjtsFibIbrMiLhYApQUH6hvQXLIr0aYP4DJjnxMCUQ+A6YFA1nzaW7MY8Akn0PPhN0H8eebmRzATKtGAcpXlSVDI3wimmxWONo4uDgeflGC5A9ClLrBcW60OH3wtFQDnQTI5SO2tEqBE58DmHOq/wJK8hNIQF97NaalBjjrr8F29zxWRiwI2W5iwn4bwZc1aK87S4FivRGxReWGY0Mh9Nft/PeGC7wWDtNJVF09jv25WThTkIZ5bwWul+5Cx8MSDNaWw19TAIgMvKYSRKO/KHB0cACNF4qUoVBJytubeqdCRlYKtmfoYcxLw60T6agtTsXRfanI2ZNOlI1yYzodXGtV2bqH/X29eHL5NJgvbfLIiVrJo3aqMPRKg7LCTFwq2oEbxdkwndqGSmMaKo5l4dzhTBTmbUXpkWR8czEwX8mFueo+zPfuwP7oAfymSvwG8m1WtuuJzVsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;TableFish Live stream and answer app&quot;
        title=&quot;&quot;
        src=&quot;/static/796fe17faa75efea219b33ebac546cd3/40fad/screens.png&quot;
        srcset=&quot;/static/796fe17faa75efea219b33ebac546cd3/707e9/screens.png 148w,
/static/796fe17faa75efea219b33ebac546cd3/649e0/screens.png 295w,
/static/796fe17faa75efea219b33ebac546cd3/40fad/screens.png 590w,
/static/796fe17faa75efea219b33ebac546cd3/b3fef/screens.png 885w,
/static/796fe17faa75efea219b33ebac546cd3/301c0/screens.png 1180w,
/static/796fe17faa75efea219b33ebac546cd3/b5a53/screens.png 1770w,
/static/796fe17faa75efea219b33ebac546cd3/06def/screens.png 2232w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;Table.fish is growing rapidly in a world where pandemic has shut down mass gatherings and public events. This is a story about how we have managed to build a platform in days that can handle hundreds of users simultaneously, let scorers mark over 10,000 answers in minutes, and most importantly, raise over €2,500 for charity.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/65cd4befbfbad3c872d576f34052c2f3/0f040/growth.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 61.83333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABu0lEQVQoz4VTi3KcMAzk/z+wl6ZJD44kB8fT2ICNbbaSgAxJO1PNCFmyjVYrOQkhQGsNM054LxrcshR5nqOuayzjCN21eDwqNORXdYPFDhh1J7G2bUhbrOuKQxL+mVJKHLd4WGtFp2mCdw52ninZKP5ISf3i4OxM61GU4wwqxiia8GdZFsmy0prlyBhPmQ+Rc3v88xzdW/fzgpBhs3jvZfOwR+azf+iXOP0oUFWBkH9BKJn+Yw8Vf91QBj0gqh6REiTMw8Hhd2R/WdoPO6JIMW8MfN+J5XJ5L2F03IRPLv6FjC5viEipKWFQCKpDMFp4jzuv0hRHnWSULEEuboj4YKCamOxAnxiIs0HDUzXRuq0pe6OiLJgKKpnRcWOOjfVsg8W6jIgDzVr7jmjuxEtP49TBzS3c1NJcKrI1jK6p0gmJIPMLnGoxd3dYU2FucsxVirG8QhcZuscbFCXtjUU/TOj1BGVmWhP/2og/2QWeSkk8lZe+/ET++gOXp2e8/M5wuZD9dcXTc4bso0J6+8Dt7Y5rmou93XKUZSEvquvo1ZQlBqKiKIod4T6Y3weW+Vwjd3jj9rDn7p+bx/4fdwGnuegqXecAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;TableFish Userbase and Contributions Growth&quot;
        title=&quot;&quot;
        src=&quot;/static/65cd4befbfbad3c872d576f34052c2f3/40fad/growth.png&quot;
        srcset=&quot;/static/65cd4befbfbad3c872d576f34052c2f3/707e9/growth.png 148w,
/static/65cd4befbfbad3c872d576f34052c2f3/649e0/growth.png 295w,
/static/65cd4befbfbad3c872d576f34052c2f3/40fad/growth.png 590w,
/static/65cd4befbfbad3c872d576f34052c2f3/b3fef/growth.png 885w,
/static/65cd4befbfbad3c872d576f34052c2f3/301c0/growth.png 1180w,
/static/65cd4befbfbad3c872d576f34052c2f3/0f040/growth.png 1200w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;h2&gt;A long long time ago…&lt;/h2&gt;
&lt;p&gt;In mid march 2020, before the total lockdown we have now, when working from home was recommended Chris (my &lt;a href=&quot;https://exante.io&quot;&gt;Exante&lt;/a&gt; co-founder) invited me to join a virtual quiz. He wanted to host 10 teams, submitting answers over whatsapp and calculating scores in excel. Managing it was a nightmare. Sending in answers was a pain. It took him ages to score answers. But people still sent him ‘entry fees’, just like a real life table quiz. I thought we could do much better…&lt;/p&gt;
&lt;p&gt;By the following quiz, I had a firebase application up and running. It let you submit answers through a form, and it displayed them on an admin page with some buttons to mark each answer correct or incorrect. The scoreboard could then be displayed to each user, which updated as scores came in. I was happy.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/45ddb790330a54c78ac9e81e30f92e6d/1797e/screen1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 72.04678362573101%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAADDUlEQVQ4yyWUSY/bRhSE+YeDHHPLKcgttwQ5BkFsx4iNBAEGSGAM4BmPZrRQoiju+9bcKVGUNPYcy0Xq0OhGk/xU71U9SZ6jQ18vEVkaEp7L0EGd+NiLAEMZ4VTFuHQC57bgfl1Dm6NvMjS5jzxxkAQ6LG0Bx9pC8m0d7m6NxNYQWzsUBEaeBxEG6Ak8cn3el7hMwBIvQ4sToSfedUWEOnVRxBZsTUZAQZIICDBV5L6FOvbQUN2B6kbQuc3wcqy55zhWGZUV2JcZ9jy3RUJYiIrfJK6G1DeRRS6k2DHgqvKkLuW5ijwCr8rOLOt5X+HMNXQVno8tLuPqGyrkXZOjyyMqdJEGFgTFSL6pwZDnLHsDT1MmpX0eY6gSnAgcYc9Dhy/nAz6fDtz303m8G6F9LSa1gtCciqXQNREYKjKP6lh+kwSo2McqcrBnjw4sr29Yap3jdGjwfOpwOe1x6lv0VN1VAmWeIol9iCy+muLIT4jkGRu7gAgMJJYKW1nCN7YIbQOliFFkbANNuZbcTvCePW35gxUrStk/wXck17MhfvsVxfff4Ls/v8XN/C0q14C1XcBWN4gdcyproKtHAgZCR4eHbjQoRVPEqESILLRZckCgrsB8uIV//z/k5Qc4xhxNZKDLHBoTMi40pstpDhfzeCHsfCjovODzFC0NLAlKA5OZpCkWS9M3M15oeMnZt9BEaq3hM1eZy6BHNuE+e+tO8C99OcHPbYqePW6yAAWfxZ5OYzxI2laG9c9r9H/8hLuHn7GQ3yPWllCf7mCu59gtHyFo2LlJGexRLZ1vE+YyQic85pAtY0Whs0VKMyVlvcD67e8IfvkB7/7+Ebef3qCwFRS+ipovNqmFA0dsqKMJ0pcMfuGhzWyqNxgzhtrdwdc3nDAL0np2j/vb/zBfzeAsnxAonEnlEd52jlCXOZLK9FHGj5rEmkB1bHJEeeepfK4iZBosdQV/HL3V7A4f/32H1V+v8OHmFR4ebqAtPkJ5vOWLK/5hKCz5CqxiZnWEMVrC202wgDBH20CnEJfT9hXMOuopMFs/WAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;The old scoring page&quot;
        title=&quot;&quot;
        src=&quot;/static/45ddb790330a54c78ac9e81e30f92e6d/40fad/screen1.png&quot;
        srcset=&quot;/static/45ddb790330a54c78ac9e81e30f92e6d/707e9/screen1.png 148w,
/static/45ddb790330a54c78ac9e81e30f92e6d/649e0/screen1.png 295w,
/static/45ddb790330a54c78ac9e81e30f92e6d/40fad/screen1.png 590w,
/static/45ddb790330a54c78ac9e81e30f92e6d/b3fef/screen1.png 885w,
/static/45ddb790330a54c78ac9e81e30f92e6d/301c0/screen1.png 1180w,
/static/45ddb790330a54c78ac9e81e30f92e6d/1797e/screen1.png 1710w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;(Yes, above image is a photo of a screen…It’s all I could find without spinning up an old version. You get the idea.)&lt;/p&gt;
&lt;h2&gt;3…2…1…&lt;/h2&gt;
&lt;p&gt;Next quiz (Friday, 20th March) we had a few more teams but were operating off a default cloudfront domain. Friends and family started to sign up. We had 28 teams in total. Assuming about 4 people on a team, that’s over a hundred people on the second outing! And our “pay what you want” model was working. Over €200 was contributed after the quiz.&lt;/p&gt;
&lt;p&gt;The ‘show’ went well. Some minor bugs. A couple teams accidentally submitting answers early. An easy fix with a confirmation dialog. A missing scoresheet. hmm. Ok, I’ll investigate.&lt;/p&gt;
&lt;p&gt;I spent a weekend on it and by the next quiz on Tuesday, we had a domain name, much nicer UX, better game controls for the quiz master and a little more functionality for scoring. It felt good to have real users on my system, but also terrifying to think about what would happen if it broke…&lt;/p&gt;
&lt;h2&gt;Double Trouble.&lt;/h2&gt;
&lt;p&gt;The next quiz blew us away. We had nearly twice the teams join us. Most of them we didn’t know. and with that growth came some growing pains.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It turns out you can’t trust user input. Even if you think you’re being careful. One team managed to set their team name to ‘undefined’. It shouldn’t have been possible, but it happened. The result was our scoring page threw an error and completely crashed after the first round. Thankfully I was able to push a frantic update mid-quiz to handle the error.&lt;/li&gt;
&lt;li&gt;I thought I was out of the woods. Sadly not by a long shot. As soon as the scoring page came back, our scorers started experiencing massive lag. I had set up the paging slightly wrong. Each new score sheet appeared after the last, but didn’t replace it. Corrected score sheets were invisible to the scorer, but still in the DOM, taking up memory. Another live fix was necessary. This time, I only had the time to implement a crude drop down to let scorers skip ahead to a page in multiples of 10. Now they could proceed with frequent refreshes and at least keep up with the incoming answer sheets.&lt;/li&gt;
&lt;li&gt;My heart rate must have been over 200 at this stage. I had just returned to the living room where my wife and her family were doing the quiz. I sat down, opened a beer, ready to enjoy the second half of the quiz. Then it ALL went haywire.&lt;br&gt;
The answer sheets stopped loading for players. The admin panel stopped updating the game state. The scoring stopped updating. uhh…I ran back upstairs.&lt;br&gt;
We had hit the free usage limit on firebase. 80,000 database reads. I was kicking myself. Firstly, why didn’t I see this coming? Secondly, how could the system be making 80,000 reads? That’s madness!&lt;br&gt;
Thankfully the fix for this one simply involved a credit card.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By the end of the show we had made 180,000 db reads. Something was up!&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c11e41b49a7325b4093c92a4fd0c8da4/6810e/screen3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 58.014477766287484%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAACBklEQVQoz22Tu24TQRSGXUKFKOhpeQWeCCSE6GjoqdLQ8ADQIIEQBSBEAx2FEyIoEilRjBPH63W8F+/szuxczvycmdkYBxjp07Fn5/znNjMSTQ3V1QD1gO+hlEIlAn2kbnu08iqd0qiFRLYscTrLsCwqrFsJ0SmMnn++wM77HM8+rbDzYYUfvxr+0COvDZZrjXVn0OsBY6PVbDsWLqoGFyy2KtdoWFD2GqO7Twvcfpzj5sMM1+8v8OpbDa8qXC5PhKIoQGzhfdyryjJitI7/teZKqioGGt15kuPGgzluPcpw7d4cb3e7IAPrPPsnoljcTcsxtImI+N05BxUy/Pizx5tdiXffFV6PJc5LF88R+U1G0W+xAOZz+NkMyHNgtYIXIgUZzoV2jHAldlo07Pih5MhkAjo4AB0egvb2QGdn8M7FU35bMDRXGwfjCKY3cKIFtQxHj3YIEOEM6eiYOQImJ3BlBaE9jE0NUEFQSgniSNGhrkHjccpifx90zM5ZBuokD0rBt4IbaOGtheEkmkJEsSsZBsHQ0FBWaC75gE+Egr5+AV6+gJtOoc8zKN5spINQjgc3tGNbUPPoLQuGSP8lHDyZQkznkJNTSBMmGkRok4T/eyhhotpuQwl27tlKZWEznrI1YQSpko3Y5gJdZsg3/x/0QPptDFsOGl8L37X0cvTAn5cUBvwbJVeWIE+VZpAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Firestore usage graph&quot;
        title=&quot;&quot;
        src=&quot;/static/c11e41b49a7325b4093c92a4fd0c8da4/40fad/screen3.png&quot;
        srcset=&quot;/static/c11e41b49a7325b4093c92a4fd0c8da4/707e9/screen3.png 148w,
/static/c11e41b49a7325b4093c92a4fd0c8da4/649e0/screen3.png 295w,
/static/c11e41b49a7325b4093c92a4fd0c8da4/40fad/screen3.png 590w,
/static/c11e41b49a7325b4093c92a4fd0c8da4/b3fef/screen3.png 885w,
/static/c11e41b49a7325b4093c92a4fd0c8da4/301c0/screen3.png 1180w,
/static/c11e41b49a7325b4093c92a4fd0c8da4/b5a53/screen3.png 1770w,
/static/c11e41b49a7325b4093c92a4fd0c8da4/6810e/screen3.png 1934w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;h2&gt;What’s up doc?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Simply answer:&lt;/strong&gt; The scoreboard was inefficient.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Less simple answer:&lt;/strong&gt; The scoreboard was embarrassingly inefficient, I’m almost too ashamed to share it.&lt;/p&gt;
&lt;p&gt;The first (and quickest, at the time) solution that occurred to me was to create a scoreboard component, get every user in the game, get every answer sheet for each of those users, then display them in a table. I obviously didn’t think about it very hard. This approach resulted in the scoreboard being generated fresh for every user that viewed it.&lt;/p&gt;
&lt;p&gt;For a rough estimate: 80 users reading 80 other users with 8 answer sheets and 8 rounds. 80 * 80 * 8 * 8 = 409,600&lt;/p&gt;
&lt;p&gt;The actual number is much smaller because the scoreboard for the first round requires less reads in total. But you get the idea.&lt;/p&gt;
&lt;p&gt;Quite frankly, if this was a university assignment I would have barely passed. Thankfully that is all behind me and the real world is much more forgiving.&lt;/p&gt;
&lt;p&gt;My updated solution is to only re-calculate the scoreboard once after each round is fully scored, and store the result somewhere that can be accessed in a single database read by all users. This reduces the db reads dramatically.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Table.fish is far from finished. As a tech challenge, I haven’t even addressed the scoring, which will make a whole post in itself. As a product, the user base keeps growing and the contributions keep coming. We’re on an exponential curve right now and it doesn’t seem to be slowing. &lt;/p&gt;
&lt;p&gt;There’s a lot to do…&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Case for Self Hosting Your Blog]]></title><description><![CDATA[We are currently witnessing an internet paradigm shift - in reverse. In The Beginning Once upon a time in the heyday of  chain mail ,  RSS…]]></description><link>https://blog.aidanbreen.com//case-for-self-hosting/</link><guid isPermaLink="false">https://blog.aidanbreen.com//case-for-self-hosting/</guid><pubDate>Fri, 15 Feb 2019 10:51:37 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;We are currently witnessing an internet paradigm shift - in reverse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;In The Beginning&lt;/h2&gt;
&lt;p&gt;Once upon a time in the heyday of &lt;a href=&quot;https://en.wikipedia.org/wiki/Chain_letter&quot;&gt;chain mail&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/RSS&quot;&gt;RSS&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Webring&quot;&gt;web rings&lt;/a&gt;, before bloggers were bloggers, when people hosted everything on their own servers, content was king.&lt;/p&gt;
&lt;p&gt;The internet was weird and wonderful and useful.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/14kqI3Y4urS3rG/giphy.gif&quot; alt=&quot;Weird&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then we all collectively&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; decided that owning our own content was for losers and posting on a centralized system with likes, shares, and reblogs was clearly the superior approach.&lt;/p&gt;
&lt;h2&gt;The Kapitalist Kontent Killers&lt;/h2&gt;
&lt;p&gt;Pretty soon, the realities of the capitalistic internet kicked in. The content that &lt;strong&gt;we&lt;/strong&gt; created was pilfered from our control, peddled to the highest bidder, and plastered in advertisements. Anything that didn’t titillate, tickle or trigger was quickly hidden by algorithmic &lt;em&gt;feeds&lt;/em&gt; and &lt;em&gt;timelines&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://en.wikipedia.org/wiki/Medium_(website)&quot;&gt;2012&lt;/a&gt;, while all this content commercialism was steaming along, a little startup called &lt;strong&gt;Medium&lt;/strong&gt; was born. It offered a simple interface and slick experience for writer and reader alike.&lt;/p&gt;
&lt;p&gt;When I discovered it in 2014, I was enamoured. After attempting to build my own blogging platform which coincidentally, also launched in 2012, (without success) I saw what I might have achieved with a bit more experience, knowledge and luck.&lt;/p&gt;
&lt;p&gt;For many people, Medium seemed like a viable escape from the grip of content killers. Without the effort of coding it yourself, hosting and managing wordpress or whatever else. Medium was cool, man. And you could be cool too.&lt;/p&gt;
&lt;p&gt;But of course, it was not to last. As I have come to learn, the cool kids are never &lt;em&gt;really&lt;/em&gt; your friends. Once again, the platform bloated and profits became the goal at the expense of user experience. Now, practically every article on the homepage is a &lt;em&gt;“member feature story”&lt;/em&gt;, costing you $5 a month to read - (The first 3 are free, yipee!) - if you can bear to read at all…&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/59f35d25e2f530fba63f78388964fa24/1d10d/image3edit2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 56.30630630630631%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB+UlEQVQoz01Ty24TQRD0/0ucLeUCJ0DiwAkiLlEUATKCA9jEdvyO7fV63+9nrBRVk6zDSK2Z6Z6urq7t7fm+j4NlIYoipGlqzHVd1E2DMAxhHQ44Ho/GV5YlmrZFczohvr9HcHuL1HEQr9fIiFMw3lPS39EIYwaDIEAcx7BYQOtAsMViYWzNpKqu0RQFHrgnjAXDIeLlEuFkgoy5BlCMJuOxAar5UExcVm3JJEkSeJ4Hh3ftZVWhImDF5IjvUrKKCBwxXuQ5csZ6WZZBltNR0NG1Lp9t26bdDlB3l7vkkG+722F6d4fZfG5yZb3uIMAOtCITSbFjwna7Nab29/u9Ya+CDgvNCDYn2JKS6K00PgP+DyxAMRWQQDabjUmQya83YruYzYy+YhuRgGGooCpq71rvzkZPmtoVY4dnn/pJa91dni0CR9RfJJTXk/CibrOl1WplGLx8FI6Q5xvzaPZujzAIUVLDUkQ4OjmL5M+sDUP1LRZqQUx93zuPzdN6wOOpxmNb4hT7T64iQ8KckERkCd9rZAxDUZ1Op2YONToyxzmirFv03w/w+uNP9N99R//tN1x8+IFXb75i8GuOJuE0xIkZ7ogf5jyHQhXL8rmCmT1Wr6oGV4MZLq//4MvNCJ+vh/h09RuXN2Osdj7/mBpZSs01YtQzZ55a/gfkrzcmTEsGxwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Mediums ui&quot;
        title=&quot;&quot;
        src=&quot;/static/59f35d25e2f530fba63f78388964fa24/40fad/image3edit2.png&quot;
        srcset=&quot;/static/59f35d25e2f530fba63f78388964fa24/707e9/image3edit2.png 148w,
/static/59f35d25e2f530fba63f78388964fa24/649e0/image3edit2.png 295w,
/static/59f35d25e2f530fba63f78388964fa24/40fad/image3edit2.png 590w,
/static/59f35d25e2f530fba63f78388964fa24/b3fef/image3edit2.png 885w,
/static/59f35d25e2f530fba63f78388964fa24/1d10d/image3edit2.png 888w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;But whatever, right? I am not my platform. My articles are still free. I can survive.&lt;/p&gt;
&lt;p&gt;Maybe not. There are reports of articles becoming &lt;a href=&quot;https://medium.com/@dan_abramov/why-my-new-blog-isnt-on-medium-3b280282fbae&quot;&gt;monetised without consent&lt;/a&gt;. &lt;a href=&quot;https://writingcooperative.com/my-love-hate-relationship-with-medium-9c672c4303c3&quot;&gt;Biased discovery&lt;/a&gt; means getting any attention through the platform is next to impossible. About 99% of my traffic comes from Twitter, Reddit and occasionally Hacker News. That’s no way to build an audience.&lt;/p&gt;
&lt;p&gt;And I’m not the only one to identify that. In 2019, many writers are &lt;a href=&quot;https://www.joshjahans.com/ditching-medium/&quot;&gt;losing faith&lt;/a&gt; in &lt;a href=&quot;https://news.ycombinator.com/item?id=19034676&quot;&gt;third party&lt;/a&gt; &lt;a href=&quot;https://write.as/blog/ending-our-medium-integration&quot;&gt;services&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Everything Old is New Again.&lt;/h2&gt;
&lt;p&gt;Hosting used to be expensive, unreliable and complicated. Now, with services like &lt;a href=&quot;https://www.bluehost.com&quot;&gt;bluehost&lt;/a&gt;, &lt;a href=&quot;https://www.dreamhost.com/wordpress/&quot;&gt;dreamhost&lt;/a&gt; and even &lt;a href=&quot;https://namecheap.com&quot;&gt;namecheap&lt;/a&gt; offering out-of-the-box wordpress, hosting your own content is easier and cheaper than ever.&lt;/p&gt;
&lt;p&gt;For those with tech chops, &lt;a href=&quot;https://pages.github.com/&quot;&gt;github pages&lt;/a&gt; offers completely free static hosting. In combination with a static site generator like &lt;a href=&quot;https://gatsbyjs.org&quot;&gt;gatsby&lt;/a&gt; or &lt;a href=&quot;https://gohugo.io&quot;&gt;hugo&lt;/a&gt;, you have the ultimate in flexibility and efficiency without sacrificing control of your content.&lt;/p&gt;
&lt;p&gt;In 2019, the tools available to independent bloggers / writers have (sort of&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;) caught up with platforms that no longer offer meaningful features.&lt;/p&gt;
&lt;h2&gt;The catch&lt;/h2&gt;
&lt;p&gt;This all sounds great, but people moved to third party platforms for a reason. So what are we missing out on?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discovery&lt;/strong&gt; is up to you. Platforms don’t do much, but they don’t do nothing. If you’re not actively sharing your content and building an audience externally, you may as well be locked in a dark room whispering to yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interaction&lt;/strong&gt; is difficult. Without the platform to handle user accounts, nobody can leave a comment. “But Wordpress has comments” I hear you moan. Yes. It does. And nobody but bots uses it. You could use a third party service like &lt;a href=&quot;https://disqus.com/&quot;&gt;disqus&lt;/a&gt;, I don’t think usage would be high. Personally, I’m just hoping people will tweet me to discuss.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interaction (Again)&lt;/strong&gt; with meaningful light feedback features like ‘like’ and ‘applause’ buttons is impossible without using a third party service.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Retention&lt;/strong&gt; of your readers is going to be tricky. With algorithmic feeds, they will be spoon fed the content they appear to like better. It’s a game you can play to get people coming back. But on a self hosted blog, your only option is a mailing list or other social media / content discovery services, like twitter, reddit etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Conclusion&lt;/h2&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ab7feca1009979d7db20d11bbc348fc5/08b6c/stats.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 37.051039697542535%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAk0lEQVQoz41RCwrFIAzb/S8qTp2KiJ+pHREUfHtsC4QqtCFNt9Ya1VrpCej5ym08UkoUQqAYY+f4o76JlFJWQcAYQ0IIUkrRvu+9MsZISjld/roGIGatnaI3h+d5/l35CdgIsS2CWuvuzDk3RUfTE4ExswjmnGeG3vvOL0eAEOZuR4FDzjkdx9GzRH27PAAj6B8ZXpwTJ6FAgnLIAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Medium Stats&quot;
        title=&quot;&quot;
        src=&quot;/static/ab7feca1009979d7db20d11bbc348fc5/40fad/stats.png&quot;
        srcset=&quot;/static/ab7feca1009979d7db20d11bbc348fc5/707e9/stats.png 148w,
/static/ab7feca1009979d7db20d11bbc348fc5/649e0/stats.png 295w,
/static/ab7feca1009979d7db20d11bbc348fc5/40fad/stats.png 590w,
/static/ab7feca1009979d7db20d11bbc348fc5/b3fef/stats.png 885w,
/static/ab7feca1009979d7db20d11bbc348fc5/08b6c/stats.png 1058w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;This is one of my more popular posts from last year. Over 5 thousand views, not massive but I’m proud. &lt;strong&gt;Less than 4% of those views came from the platform.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This bothered me, but not enough to actually &lt;strong&gt;do&lt;/strong&gt; anything. Then, out of curiosity, I tried exporting my posts. Here’s what I got:&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cd79611c6906200e2a4b6ff8575e41c4/719ac/export.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 56.356275303643734%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA10lEQVQoz6WQWxKCMAxFuxtFBcUWSZtS3P+mYhNaKYgzPj7O5OZBuKlqWqBD09H+pJ9UtVnEb1AX7ai+3F4WrfWnqNLh1pKvHZ6vlo6rhf+gWuPoHF3+ct7myVeNxO9YNfEda3ZqhF3BOt9t9DMKAMkiEljGCT04uvV2AuxSQ+rDlPcF2vSkEB2NwQv3MZK05Kk2hldkVhieOi1EGRg8UhhiHFB0ZqqlujBr7uVvOLY6LnTRYXYVwjQQcl7osai9i6YDUja+Gf/VewYJ/awXpBnu81VbsMMHl3NhcMgLnrEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Medium Export&quot;
        title=&quot;&quot;
        src=&quot;/static/cd79611c6906200e2a4b6ff8575e41c4/40fad/export.png&quot;
        srcset=&quot;/static/cd79611c6906200e2a4b6ff8575e41c4/707e9/export.png 148w,
/static/cd79611c6906200e2a4b6ff8575e41c4/649e0/export.png 295w,
/static/cd79611c6906200e2a4b6ff8575e41c4/40fad/export.png 590w,
/static/cd79611c6906200e2a4b6ff8575e41c4/b3fef/export.png 885w,
/static/cd79611c6906200e2a4b6ff8575e41c4/301c0/export.png 1180w,
/static/cd79611c6906200e2a4b6ff8575e41c4/b5a53/export.png 1770w,
/static/cd79611c6906200e2a4b6ff8575e41c4/719ac/export.png 2470w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;That is a HTML file. It looks ok if you open it in your browser. But the content is a single line of mangled HTML tags, arbitrary css and nonsense. &lt;strong&gt;This was enough to make me do something&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Migrating my old posts is a nightmare that involves copying and pasting text, downloading and reformatting images and basically re-writing my entire blog. Every new post I make on Medium would mean hours of work in the future.&lt;/p&gt;
&lt;p&gt;Now, new posts are written in plaintext markdown files, with a simple directory structure: one folder per post. That folder contains all the images and assets used in the post. It is simple, maintainable and most of all, easy to migrate.&lt;/p&gt;
&lt;p&gt;I hesitate to use the term “future proof” but it’s a step in the right direction.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f886675f97f76e0bfa1e4c2e6d28e883/66e30/image2edit2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 80px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 105%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAABYlAAAWJQFJUiTwAAABnklEQVQ4y71V2YrCQBDM//+SN6J4J0ZRvIiKSoxHxNunXqpgQhwnLoK7D81o93RN1XT3xLpcLvJNs/4F8Hw+R6tucf9HDOMJt9vtOwyPx6OcTifxfV/CMOR/nW0ioC7ner3KdruV5XLJdbPZyGw2o/8dqJV0d0ommD0eDzKFmQ6P/7b0AGSBxWq1IrNGoyHT6VTu93t0sGKpg79IRhIkOo4jo9GIYIvFQlqtFiUfDgfG1us1FZhALd2Bze12m4mDwYCg8A+HQ/p7vZ5UKpUI8K1kSOl0OkzqdrsEcF1X6vW67HY7KZfLZFwqlcTzPCrS+9TIsNlsSr/fJ1ihUJB0Ok0fAG3blvF4TFBdcnSH8QDYQCokF4tFyeVyks/nJZvN0nAA2gexIAhe2uipKAju93upVqtko8DUqmw+nzOOa4hX3MgQl41qIiGTyZARQJR0FGkymVCBXpgnQBMo5KdSKUoFGEDBDkWDEp3dSx/GQbEZk4H7rNVqLAZ6EZMDMzW3cfT0Tao1AAAzTUoiYNJ8mt7GpHfx1/fwk8f1Tz4BP9qXMFjkijRfAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Applause button&quot;
        title=&quot;&quot;
        src=&quot;/static/f886675f97f76e0bfa1e4c2e6d28e883/66e30/image2edit2.png&quot;
        srcset=&quot;/static/f886675f97f76e0bfa1e4c2e6d28e883/66e30/image2edit2.png 80w&quot;
        sizes=&quot;(max-width: 80px) 100vw, 80px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;In the end, Medium, you made my decision for me. Thank you. Next.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;  An argument could be made that we were all hoodwinked by the Mark Zuckerbergs of this world in this regard.&lt;/p&gt;
&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;  I have many gripes with current blogging tools that I will cover in a later post. Wordpress is an over complicated disaster for simple blogging. Gatsby is complex, and while the final product is lovely, the setup is not.&lt;/p&gt;
&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Vivisection of OpenTable widget.]]></title><description><![CDATA[You may be familiar with OpenTable, a service built around restaurant bookings. One of their main features is an embeddable widget that…]]></description><link>https://blog.aidanbreen.com//vivisection-of-opentable-widget/</link><guid isPermaLink="false">https://blog.aidanbreen.com//vivisection-of-opentable-widget/</guid><pubDate>Wed, 06 Feb 2019 12:46:37 GMT</pubDate><content:encoded>&lt;p&gt;You may be familiar with OpenTable, a service built around restaurant bookings. One of their main features is an embeddable widget that restaurants can insert in their website to let patrons reserve a table. Here’s what it looks like in the wild:&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d4493fc8415fa3782f9b4f6b862c9631/3a642/screenshot1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 54.60937500000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABZklEQVQoz31Sy3KCQBDkw3NKTrnllD/yomKS0lhRPCAaKQkKIvIS7WxPsWQllWxV184MO73TvViXywXj8Ri9Xg95noOLtev12oJ5XdctzuezEddswGqXwXFdWEmSYDKZCIIg+JPQBInMXB1CmFZw15+wyrIEURQFsiwTAnN18+63qqqknyva72FRpgalLJdLzOdzLBYLuEoC8+l0itls1uaMHceBv9lIHwch8Xa7hcXJNCGleJ6H4XAI27bFW+YkHwwGUnsZjWTv9/tyse7nMLRMJtTFOI5BT7UEesOD4lMj0TSAVVOdTKgJC7UHquD7PtI0xfF4RBTF8u10Oil/IvCag/0K7+4B6/tHbJ6eUSipedYhzBvCw2qNXRiKH9obPb3Eqp59hTi8TZCM35F83Er+IeSU3FUTG9upm8M3MV9VTSpQFvySXJZF29CF6e9NzEs1mlr7ynwIemT+PlqiSfLfpeYrfwMsc0teW41GqwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;OpenTable widget on http://thehillpub.ie/&quot;
        title=&quot;&quot;
        src=&quot;/static/d4493fc8415fa3782f9b4f6b862c9631/40fad/screenshot1.png&quot;
        srcset=&quot;/static/d4493fc8415fa3782f9b4f6b862c9631/707e9/screenshot1.png 148w,
/static/d4493fc8415fa3782f9b4f6b862c9631/649e0/screenshot1.png 295w,
/static/d4493fc8415fa3782f9b4f6b862c9631/40fad/screenshot1.png 590w,
/static/d4493fc8415fa3782f9b4f6b862c9631/b3fef/screenshot1.png 885w,
/static/d4493fc8415fa3782f9b4f6b862c9631/301c0/screenshot1.png 1180w,
/static/d4493fc8415fa3782f9b4f6b862c9631/b5a53/screenshot1.png 1770w,
/static/d4493fc8415fa3782f9b4f6b862c9631/3a642/screenshot1.png 2560w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;This widget was generated by the OpenTable widget builder page like so:&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3902295d05ad6ccc25b62c3abb8c1cd4/0dba9/screenshot2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 50.47095761381476%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABVElEQVQoz4WT3U7DMAyF9/5Pg4QQCAESL4DEI6xsS/PTNE6attsuDna6jA0EXJzadeovjp2uxnGE9x4hBEzTVJRSKrGcc3mXb+rapUr8eER4e0f3+ILc91hJ0DlXkud5Lh+J74yBP8VjjGWTH5L4MIB2CmHdYGS/AKU6SZoYOMvubL1YAJmtrBPRr4oMiswRVgGq7RZKqbJjlgoDwT48IT6/Im53IEmQRF6/0ilWoKwzULcttNbLjilipIju5g7m9h6Jj0NDKgl/VsngApz4sWkaNCyBGu5dygPS4YDIDY/s00Ul/wJH6REHZKoDH22/35dBkPS19u4fWNVS4ekKCESA4oslCkuPaBnYwFP9rjrtegvOPaw68DHl6sii1gattdgZB9N1cD0Pqg9sv+R5eDHQNbBe0KpaoWoNNgxtdMdQiw9tsW4tv7PaxVe2g7fu/GNI/ickM/scW/T2JgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;screenshot2&quot;
        title=&quot;&quot;
        src=&quot;/static/3902295d05ad6ccc25b62c3abb8c1cd4/40fad/screenshot2.png&quot;
        srcset=&quot;/static/3902295d05ad6ccc25b62c3abb8c1cd4/707e9/screenshot2.png 148w,
/static/3902295d05ad6ccc25b62c3abb8c1cd4/649e0/screenshot2.png 295w,
/static/3902295d05ad6ccc25b62c3abb8c1cd4/40fad/screenshot2.png 590w,
/static/3902295d05ad6ccc25b62c3abb8c1cd4/b3fef/screenshot2.png 885w,
/static/3902295d05ad6ccc25b62c3abb8c1cd4/301c0/screenshot2.png 1180w,
/static/3902295d05ad6ccc25b62c3abb8c1cd4/b5a53/screenshot2.png 1770w,
/static/3902295d05ad6ccc25b62c3abb8c1cd4/0dba9/screenshot2.png 2548w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;Try it yourself at: &lt;a href=&quot;https://www.opentable.com/widget/reservation/preview?rid=412810&amp;#x26;language=en-US&amp;#x26;domainId=1&amp;#x26;countryCode=US&quot;&gt;opentable&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, we can change a few settings and then just copy the code into our own website. Pretty handy, but how does it work?&lt;/p&gt;
&lt;h2&gt;The loading script&lt;/h2&gt;
&lt;p&gt;The script we copy is simply a &lt;code class=&quot;language-text&quot;&gt;&amp;lt;script/&amp;gt;&lt;/code&gt; tag, which is used to load javascript. We can simply copy the URL into a browser window to see the code that would be loaded:&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c3011ff821afad0c9f36fe90d630fb75/53c0a/screenshot3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 37.842064112587956%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABHElEQVQoz22R2Y6DMAxF8/8f2H2jhbCEHcTw0t7JySgVDxPpyHZirhfMMI6yRaGycqpcLZsXgaIsQxzhHVzTqK1rDd72Q6eyaeW6ObzVzslcr1clj4eyLAtYm339qqrkfBLUXiTGjRdzPsZvvT9No36WRRxzOByUJIkul4seXjhNUz2fzwA+wvGO99frpbZt1XWd+r7Xuq5f3u+3zPF41O120/l8DhZx7Pbufr8HKEq8LYSlY4pQwJAEp9NJjA/4UQgfS4zPRBQCvkOQbhFc/NiGJB53u53oFvb7ffiYERnVWhugI2CX7JTO5nn2O5yCBUPlv59hVfo/m+d5iLHbUYBOhmEIxD2Sgzgxwubz+Qj+O/FtC4uH7V3M5fwCo21Vfol5iZ8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Raw widget code&quot;
        title=&quot;&quot;
        src=&quot;/static/c3011ff821afad0c9f36fe90d630fb75/40fad/screenshot3.png&quot;
        srcset=&quot;/static/c3011ff821afad0c9f36fe90d630fb75/707e9/screenshot3.png 148w,
/static/c3011ff821afad0c9f36fe90d630fb75/649e0/screenshot3.png 295w,
/static/c3011ff821afad0c9f36fe90d630fb75/40fad/screenshot3.png 590w,
/static/c3011ff821afad0c9f36fe90d630fb75/b3fef/screenshot3.png 885w,
/static/c3011ff821afad0c9f36fe90d630fb75/301c0/screenshot3.png 1180w,
/static/c3011ff821afad0c9f36fe90d630fb75/b5a53/screenshot3.png 1770w,
/static/c3011ff821afad0c9f36fe90d630fb75/53c0a/screenshot3.png 2558w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;Yikes. It’s obviously been minified (as you would expect) but hope is not lost. Let’s copy this into a text editor and run some auto formatting:&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2eab4e1740bde33686b536edf1e87efb/7b9cf/screenshot4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 59.42583732057417%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABeUlEQVQoz6VT2XKDMBDjcziM8cHanOk10/7/F6laIJkkkzad6YMGezGyVisK22VkOaELE6q2R2niv1DUVuBkRdfPaP24kx6o2rjt6xvIVvsJRWMTYn5HmFe4YYaXESEQvCCkE9owwHQJLS82/EDPK6mqqe7UbYSGLcv4CXn7giwrfFo2Qh+5lhfYuKBR5SSsSLY/VeUDsF4YVTh8wM8vcHmClYl+ZniSBJ9hve4HOJ9gWLN+QOcyFXNvM2o+y+uW1cMYR7RO+JIedYKGLwzr2p6um7N3R5uVufXtpuWG/sR+hfQTW81w/d6yY10TYLu0kevFpTkG9sC7C6EelDghE9Kf6Nu6tVST7P7j36Z7IWzdQIXTNtWYSZgYH0dVrKs/1RXRswzuhN2AlF/RzxyMZpHtGhKqn+XVzX9FoSpCWkm6sN0TLNdWMoNO/+ip8RyOwj3BcaYIjMKkvx4z54TBzszgsCNoyHvNJMPN+DSMTBvSBnNA10p0VvgNNr135chaX9UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Formatted widget code&quot;
        title=&quot;&quot;
        src=&quot;/static/2eab4e1740bde33686b536edf1e87efb/40fad/screenshot4.png&quot;
        srcset=&quot;/static/2eab4e1740bde33686b536edf1e87efb/707e9/screenshot4.png 148w,
/static/2eab4e1740bde33686b536edf1e87efb/649e0/screenshot4.png 295w,
/static/2eab4e1740bde33686b536edf1e87efb/40fad/screenshot4.png 590w,
/static/2eab4e1740bde33686b536edf1e87efb/b3fef/screenshot4.png 885w,
/static/2eab4e1740bde33686b536edf1e87efb/301c0/screenshot4.png 1180w,
/static/2eab4e1740bde33686b536edf1e87efb/b5a53/screenshot4.png 1770w,
/static/2eab4e1740bde33686b536edf1e87efb/7b9cf/screenshot4.png 2090w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;Ok, that looks a bit more approachable. For reference, I’m using the Atom text editor with Atom-beautify for auto formatting.&lt;/p&gt;
&lt;h2&gt;First impressions&lt;/h2&gt;
&lt;p&gt;First thing to note here is the outer function syntax:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a &lt;strong&gt;self-executing anonymous function&lt;/strong&gt;. That is, the function is called as soon as it is loaded (“self executing”), it doesn’t have a name or a variable reference (“anonymous”). This is a common approach for plugins and widgets, and was particularly common for jQuery plugins. In my experience, I have seen this mostly using the double bracket pair syntax: &lt;code class=&quot;language-text&quot;&gt;(function(){})()&lt;/code&gt; but this does the same thing.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;!&lt;/code&gt; is simply a unary operation (a logical “not”) that forces javascript to execute the function. If the output of the function was stored in a variable, it would be negated, but since the function doesn’t return anything, and the result isn’t stored or used anywhere, it has no effect.&lt;/p&gt;
&lt;p&gt;Next thing to note is that &lt;code class=&quot;language-text&quot;&gt;window&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;document&lt;/code&gt; are passed into the function and will be referenced by &lt;code class=&quot;language-text&quot;&gt;i&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;n&lt;/code&gt; respectively inside the function.&lt;/p&gt;
&lt;p&gt;Let’s find and replace the occurrences of i and n for the sake of clarity…&lt;/p&gt;
&lt;h2&gt;Defaults and the Comma Operator&lt;/h2&gt;
&lt;p&gt;Looking at the internals of the function, we see that the widget starts by assigning stuff to the window, but it does this is a fairly interesting way, first by checking for an existing &lt;code class=&quot;language-text&quot;&gt;window.OT&lt;/code&gt; value using &lt;code class=&quot;language-text&quot;&gt;||&lt;/code&gt; , and if none exists (ie. if &lt;code class=&quot;language-text&quot;&gt;window.OT&lt;/code&gt; is undefined), the code after the &lt;code class=&quot;language-text&quot;&gt;||&lt;/code&gt; is executed.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9e0887a4abc6871c5bac50cb6ffde105/10a4e/screenshot5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 15.793470007593013%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAc0lEQVQI142MSxLCIBAFcxsDEiSEGYKST1nq/W/Uoh7ALPq9TVd3xguSdlZ9IrozyUKKV1yYcWPBDHIcr3S2TRgzVReqrBTduM0vSn6Qyv0b/oj2AOdL/gVPLhJiJWkl5gUnGz6WJii9m7BDwrTv/2Ca9wbcWl5z/4DeHQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Formatted code detail&quot;
        title=&quot;&quot;
        src=&quot;/static/9e0887a4abc6871c5bac50cb6ffde105/40fad/screenshot5.png&quot;
        srcset=&quot;/static/9e0887a4abc6871c5bac50cb6ffde105/707e9/screenshot5.png 148w,
/static/9e0887a4abc6871c5bac50cb6ffde105/649e0/screenshot5.png 295w,
/static/9e0887a4abc6871c5bac50cb6ffde105/40fad/screenshot5.png 590w,
/static/9e0887a4abc6871c5bac50cb6ffde105/b3fef/screenshot5.png 885w,
/static/9e0887a4abc6871c5bac50cb6ffde105/301c0/screenshot5.png 1180w,
/static/9e0887a4abc6871c5bac50cb6ffde105/10a4e/screenshot5.png 1317w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why would we bother checking for an existing window.OT value?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I assume this is to handle a website embedding the code in more than one place. This way we only ever execute the code after the &lt;code class=&quot;language-text&quot;&gt;||&lt;/code&gt; once.&lt;/p&gt;
&lt;p&gt;So, after the &lt;code class=&quot;language-text&quot;&gt;||&lt;/code&gt;…&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1ef065ec876a4fd5ad36691d3a1fcd53/f98df/screenshot6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 21.261682242990652%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA0UlEQVQY0y2QXXKEIBAGPU2srFkV5R9EZLOV1+T+h+mM7j581QM19ADd52jwayGZitERrSL3OXIbHf2w0N+UZLnqj+HFflBvrm8urx5JN0wOtQaiL9j4g4/fhPJAx4PZFEb/QLnELFFrvPYm11A2sYbMLDxz9iif6L6UZ1QWLcJx/8XUA3ck7J7Q+WTBt4IJicNlmi1ks7GZRJW1NzvRPrG+McnFursKInRyYGeuf9jaCG3DHxtmS8J8CV3OBJ+pNrNrkRmpRVzlq6KW1wmdDPsHLj6C61j9tIUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Further code detail&quot;
        title=&quot;&quot;
        src=&quot;/static/1ef065ec876a4fd5ad36691d3a1fcd53/40fad/screenshot6.png&quot;
        srcset=&quot;/static/1ef065ec876a4fd5ad36691d3a1fcd53/707e9/screenshot6.png 148w,
/static/1ef065ec876a4fd5ad36691d3a1fcd53/649e0/screenshot6.png 295w,
/static/1ef065ec876a4fd5ad36691d3a1fcd53/40fad/screenshot6.png 590w,
/static/1ef065ec876a4fd5ad36691d3a1fcd53/b3fef/screenshot6.png 885w,
/static/1ef065ec876a4fd5ad36691d3a1fcd53/301c0/screenshot6.png 1180w,
/static/1ef065ec876a4fd5ad36691d3a1fcd53/f98df/screenshot6.png 1712w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I have “folded” each function to highlight the comma operator syntax.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We see an empty object followed by a comma…weird.&lt;/p&gt;
&lt;p&gt;Usually we would expect a comma to be inside a function declaration, an array or an object, right? In this case, the comma is used as an &lt;strong&gt;operator.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand.&lt;br&gt;
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Based on that description, the empty object is “evaluated” [see note below], then a function is declared and assigned to OT.createNS [note 2], this function is then called a few times.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note: Why the empty object? If each step is evaluated and only the last value is returned, what use is the empty object?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By playing around a bit, I have discovered that the empty object is necessary to avoid an error when assigning the &lt;code class=&quot;language-text&quot;&gt;OT.createNS&lt;/code&gt; function right after the &lt;code class=&quot;language-text&quot;&gt;||&lt;/code&gt; operator. Specifically: “Uncaught ReferenceError: Invalid left-hand side in assignment”.&lt;/p&gt;
&lt;h2&gt;The CreateNS function&lt;/h2&gt;
&lt;p&gt;This function gets called a few times, and as far as I can tell, it is simply used to set up the OT object with nested attributes. I think NS might stand for Name Space, but that’s a guess.&lt;/p&gt;
&lt;p&gt;In short, it converts the string “OT.something.else.entirely” to an object.&lt;/p&gt;
&lt;p&gt;It’s fairly straightforward, with the only interesting bit being the use of the &lt;code class=&quot;language-text&quot;&gt;void&lt;/code&gt; operator:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The void operator evaluates the given expression and then returns undefined.&lt;br&gt;
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The OT functions&lt;/h2&gt;
&lt;p&gt;The next few lines use the createNS function to ensure the OT object has an attribute of a certain name, and then assigns a function to each of these attributes. The functions are self explanatory:&lt;/p&gt;
&lt;p&gt;createScriptTag, createCssTag, createOcTag, createIFrameTag, loadJQuery&lt;/p&gt;
&lt;h2&gt;The Modal&lt;/h2&gt;
&lt;p&gt;All of this extra work is building up to being able to inject a “modal” into the host page. The modal in question appears when you click the “find a table” button:&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c08adfb0ae8248ddd124ea411dc05391/6b731/screenshot7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 56.78048780487806%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB3UlEQVQoz4VSTU8aURT1d/AlCBNkIDAzoAv9Key01FmY7lTYaNi4VNGa+NVoCA22pmpiNI0u3Lgx/gZd6EITP0ARBWbI6b2vmemojb7k5Nx3350z5753OzRNw3tIJpP4qMaJDlVVRcCsKIpgC7yPx+MiZmEnLAFnPcMWjMVikCQJUigEf2eniDkXiUTg8Xjgcrngdrvh9XoF897n84lzS1g4tNrij9PpNPL5PEZGRzGWzSKXyyFLrOs6MpmMwOfBT8gMDEKnWB8aeiFmO2T4/X4Ui0Xwat7cot1o4LFeR6VSgWma+N+6prMUmbEEbYcaJQOBAEqlkih8rlZhtlpoEgzDQIu53YZRe8Tdzy3cbmyjtrmDi+MTqIEglETin0ONL5wSfG+WwyoJNsjhA3GTuP5QQ43cPl1e4XxsAucTk7jM5XH2+wCpVOqVIAVsuysYxHq5DBwe4an4A8bWLozvGzCZ1zfR3t5Dq/wLJrGxuw8Q35+eoaevzxa0W2bB7qiM8eEv2Ftdw+rCAlZm57A0M0MoYKlQ+MuExalpfJv7ivLyChbn58WDvnkUbluhRL8cRX+CZrG3F92yjFA4DOkVOBemUZFpKuRo9IWY7VBluyxKf2NwzHfzHpwD7nzlPyt111TfGR4nAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Popup modal&quot;
        title=&quot;&quot;
        src=&quot;/static/c08adfb0ae8248ddd124ea411dc05391/40fad/screenshot7.png&quot;
        srcset=&quot;/static/c08adfb0ae8248ddd124ea411dc05391/707e9/screenshot7.png 148w,
/static/c08adfb0ae8248ddd124ea411dc05391/649e0/screenshot7.png 295w,
/static/c08adfb0ae8248ddd124ea411dc05391/40fad/screenshot7.png 590w,
/static/c08adfb0ae8248ddd124ea411dc05391/b3fef/screenshot7.png 885w,
/static/c08adfb0ae8248ddd124ea411dc05391/301c0/screenshot7.png 1180w,
/static/c08adfb0ae8248ddd124ea411dc05391/b5a53/screenshot7.png 1770w,
/static/c08adfb0ae8248ddd124ea411dc05391/6b731/screenshot7.png 2050w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;Modals are a fairly challenging (and controversial!) UI element. They have to sit over everything in your page, position themselves in the center of the screen, and go away when you need them to.&lt;/p&gt;
&lt;p&gt;I try to avoid them if possible, as they also lead to accessibility issues and pose a challenge for screen readers.&lt;/p&gt;
&lt;p&gt;All this leads to complex CSS and JS, which isn’t apparent in the code that we copied…so where is it?&lt;/p&gt;
&lt;p&gt;Going even further down this rabbit hole, we can inspect the modal and discover that the contents actually come from an i-frame. That means the UI and elements are really a completely separate web page, loaded from a different server, simply placed over the host website.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3202fed1688619412af66c4902be15a0/fad2e/screenshot8.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 50.511408339889854%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACH0lEQVQoz2WRWU8TYRSG+wfFYDCWihXiD0CLYNVKC25XqJEqsrSl7VAFiTfGJRAjQZZYIy2LQBDpRkuBAKGdoXWpj2cGChEunnm/b8457/cmx3TeGcDS7Oe4Vgtn7V1UNbqprH/Eqfo2KuofUHnlMadt7VTY3Jxp6qTqWhfVt3qNuRpXENNxI2trCGdwAldwkmb/OA7fKDc8I9i7P3K9ZwSHfwynMmnoxdshY648q/Ofof7SpfsDBMfXUcayKJObBCY2CIqGwtuEPm+hfMoSkrp3ZJW6uy8O05U5YVh75zk90uwZzRyQxivao/P2O0/ah+gQ3H1hau/1HxoeJdTNDg191ElC75slfANRfK9m8Q1O76vOSzkPztDbH6Hr9SK1ktByImFLHxaXIu4BzFK0tig4ng5j7/xAg/s9tvZ3hpaxCU0dwzR0DFEjvXrC8hKNhObLbVRffUiN0ydJpSgpLQ4PZoeXczc9+zg8R2d7N+bGZ1hkwxfkbm1VjBBWV0BUtvw1HGVsfIrwdIzofJzItxiR+QRTc3EhIf8SB5pkZjHJ7GKK+aWU9C0T1Vn4IazwJTInfUuYdnIlVmIqycQO6axGLJU3SKyqBitJXfMk03ky6yrpjSL5wm80TaX05xf8LSEffhYLFAt7mFLrGpGFDWIZldiaxrJuktFIZfOCRnJNMzSxphIX07j0be3usbu5iZrLsVcoUizKI6pKTu7/AFZ1UpVbeaMBAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Popup modal source&quot;
        title=&quot;&quot;
        src=&quot;/static/3202fed1688619412af66c4902be15a0/40fad/screenshot8.png&quot;
        srcset=&quot;/static/3202fed1688619412af66c4902be15a0/707e9/screenshot8.png 148w,
/static/3202fed1688619412af66c4902be15a0/649e0/screenshot8.png 295w,
/static/3202fed1688619412af66c4902be15a0/40fad/screenshot8.png 590w,
/static/3202fed1688619412af66c4902be15a0/b3fef/screenshot8.png 885w,
/static/3202fed1688619412af66c4902be15a0/301c0/screenshot8.png 1180w,
/static/3202fed1688619412af66c4902be15a0/b5a53/screenshot8.png 1770w,
/static/3202fed1688619412af66c4902be15a0/fad2e/screenshot8.png 2542w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;The code we are vivisecting really only builds the &lt;code class=&quot;language-text&quot;&gt;src&lt;/code&gt; for the iframe, passing in the details of the specific restaurant.&lt;/p&gt;
&lt;p&gt;From here, it is quite straightforward and well beyond the scope of this post. The modal i-frame is a fully functional web application (a react app if the class name “react-root” is to be believed).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We have seen some interesting patterns here — most of them (the comma operator usage for example) are fairly specific and probably the result of a preprocessor trying to produce a smaller JS. I’m not too sure what, how or why, but hopefully somebody can comment with an explanation.&lt;/p&gt;
&lt;p&gt;The whole thing, however, boils down to a fairly simple approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the user to copy a &lt;code class=&quot;language-text&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag in the host page.&lt;/li&gt;
&lt;li&gt;Use a self executing anonymous function in the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; to…&lt;/li&gt;
&lt;li&gt;Insert an &lt;code class=&quot;language-text&quot;&gt;&amp;lt;i-frame&amp;gt;&lt;/code&gt; tag close to the script tag, and…&lt;/li&gt;
&lt;li&gt;Load your widget from your own server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Applications as Configurations.]]></title><description><![CDATA[Here’s a thought: “Most software applications consist of the same basic functions in different configurations”. Let’s play with that…]]></description><link>https://blog.aidanbreen.com//applications-as-configurations/</link><guid isPermaLink="false">https://blog.aidanbreen.com//applications-as-configurations/</guid><pubDate>Wed, 30 Jan 2019 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here’s a thought:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Most software applications consist of the same basic functions in different configurations”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let’s play with that. Imagine a super-high-level language that could describe a software application tersely, but still capture the usefulness of the application.&lt;/p&gt;
&lt;h2&gt;✏️ Our app&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Example 1&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Twitter
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; login
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; send a tweet
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; follow another user
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; view a timeline of tweets&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/aido179/1734bb23182d801f4db3b3e694d54a63&quot;&gt;&lt;em&gt;further examples here&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our app needs a name, we’ll use Twitter as an example.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;login&lt;/code&gt; is pretty self explanatory and so common that we can assume it’s baked into the language. &lt;code class=&quot;language-text&quot;&gt;login&lt;/code&gt; becomes &lt;code class=&quot;language-text&quot;&gt;library.login&lt;/code&gt; indicating we will use provided library behaviour and not a custom implementation.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;send a tweet&lt;/code&gt; is a unique instance of a very common action: take some user input (data: text, image, video, audio etc) and store it somewhere (write), so it can be returned (read) later. We can specify all that in more detail later. For now, let’s imagine &lt;code class=&quot;language-text&quot;&gt;send a tweet&lt;/code&gt; just references further configuration somewhere else, and becomes &lt;code class=&quot;language-text&quot;&gt;custom.tweet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;follow another user&lt;/code&gt; again, a unique name for a common action: make a link, connection or relationship between two things, in this case, users of our app. In the case of twitter, the follower-to-followed relationship is &lt;strong&gt;many-to-one&lt;/strong&gt;. The Facebook friend-to-friend relationship is &lt;strong&gt;one-to-one&lt;/strong&gt;. On reddit, user-to-subreddit subscriptions are &lt;strong&gt;one-to-many&lt;/strong&gt;. &lt;code class=&quot;language-text&quot;&gt;follow another user&lt;/code&gt; becomes &lt;code class=&quot;language-text&quot;&gt;custom.follow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;view a timeline of tweets&lt;/code&gt; is a read action. It’s going to get some set of things, arrange them somehow and display them to the user. It will probably have nested actions, like liking, retweeting and replying but we’ll handle that later. It becomes &lt;code class=&quot;language-text&quot;&gt;custom.timeline&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ok, let’s iterate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 2&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Twitter
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; library.login
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; custom.tweet
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; custom.follow
  Allow user to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; custom.timeline&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each &lt;code class=&quot;language-text&quot;&gt;allow user to&lt;/code&gt; line corresponds to a function of the app, but may include multiple UI components. &lt;code class=&quot;language-text&quot;&gt;Library.login&lt;/code&gt; will obviously require a login page, a logout button somewhere else and other elements to update passwords and other information. &lt;code class=&quot;language-text&quot;&gt;custom.follow&lt;/code&gt; might appear in multiple places throughout the app.&lt;/p&gt;
&lt;h2&gt;🌇 Structure and views&lt;/h2&gt;
&lt;p&gt;It probably makes sense then to define the structure of the app separately, but with reference to the previously defined actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 3&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Twitter
  &lt;span class=&quot;token key atrule&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;#Allow the user do these...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.login
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.tweet
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.follow
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.timeline
  &lt;span class=&quot;token key atrule&quot;&gt;Views&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Allow the user to view these&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.landingPage
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.home
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.profile&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here I’ve split the app into &lt;code class=&quot;language-text&quot;&gt;Actions&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Views&lt;/code&gt;. We’ll consider the first view in the list (library.landingPage) to be the default or index. Again, using library means it’s built in. Allowing us to use default boilerplate like this should help speed up development. We can always come back later and use a custom view instead.&lt;/p&gt;
&lt;h2&gt;🔗 Views and Actions&lt;/h2&gt;
&lt;p&gt;Mapping actions to parts of our application structure is not trivial. Some pages will have specific actions, and some actions will be reused in many views. Each view should define exactly the actions it needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 4&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Twitter
  &lt;span class=&quot;token key atrule&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;#Allow the user do these...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.login
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.tweet
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.follow
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.timeline
  &lt;span class=&quot;token key atrule&quot;&gt;Views&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Allow the user to view these&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.landingPage
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.login
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.home
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.login.isUserloggedIn
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.tweet
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.timeline
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.profile
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.login.isUserloggedIn
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.follow&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now views have specific actions. We can now have functionality, with the added bonus that is totally clear what happens in each view.&lt;/p&gt;
&lt;p&gt;Notice how &lt;code class=&quot;language-text&quot;&gt;library.login&lt;/code&gt; is used in each view. The &lt;code class=&quot;language-text&quot;&gt;library.login&lt;/code&gt; action is like a package with many nested functions. We should be able to use specific parts of an action like this — where we don’t need to let the user login on each page, but only check that they are logged in.&lt;/p&gt;
&lt;h2&gt;🗣️ Discussion&lt;/h2&gt;
&lt;p&gt;It is conceivable that a configuration like this could be used to generate all the necessary boilerplate for an application. At this point, the application could be generated for any platform too.&lt;/p&gt;
&lt;p&gt;None of this is necessarily new. &lt;a href=&quot;https://www.meteor.com/&quot;&gt;Meteor&lt;/a&gt; provides default app generation, boilerplate login functionality, and client-server comms. &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; has &lt;a href=&quot;https://github.com/facebook/create-react-app&quot;&gt;create-react-app&lt;/a&gt; which…creates a React app for you. The &lt;a href=&quot;https://cli.vuejs.org/guide/creating-a-project.html&quot;&gt;Vue CLI&lt;/a&gt; create command will scaffold your app. In fact, tools like &lt;a href=&quot;https://yeoman.io/&quot;&gt;Yeoman&lt;/a&gt; literally allows you to define a ‘generator’ and spit out a scaffolded application. Further, &lt;a href=&quot;https://facebook.github.io/react-native/&quot;&gt;React Native&lt;/a&gt; takes a JavaScript project (basically a highly featured configuration) and generates native applications, similar to &lt;a href=&quot;https://cordova.apache.org/&quot;&gt;Cordova&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Code generation is not new. So what’s different here?&lt;/p&gt;
&lt;p&gt;In the case of Yeoman, &lt;em&gt;create-react-app&lt;/em&gt; and the VUE &lt;em&gt;create&lt;/em&gt; command, once the scaffolding is generated, it’s all up to you. You are left with standard application code and development continues as normal. What I propose here is a tight coupling where the &lt;strong&gt;configuration becomes the application&lt;/strong&gt;, not just a blueprint. That is, if you change the configuration at any time, the code will be updated to reflect that change. Write code that conflicts with the configuration, you get errors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A developer should be able to look at the configuration and know that the application strictly conforms to that definition.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the case of Meteor, with it’s highly functional login boilerplate and client-server comms (pub/sub), (which I think is wonderful, by the way) this goes a step further. User authentication and login is not a convenient package that happens to work quite well — it should be baked in to the very core of the system. With Meteor, the developer still needs to write templates that show/hide content or routes based on the users logged in state. This is silly. At best, it is inconvenient and leads to template overhead. At worst, it is a security risk when, though accident or inexperience, a developer does not handle authentication correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It should be impossible to create insecure apps!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a view is defined like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;Views&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; custom.home
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; library.login.isUserloggedIn
        &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There should be nothing else to do. If the user isn’t properly authenticated, it isn’t accessible. That’s it. No fiddling with client side routers, entry events, or app-wide templates or anything else. The config is God. The config wins.&lt;/p&gt;
&lt;p&gt;Finally, (for now) there are massive benefits when it comes to deployment and maintenance. Deterministic configurations should provide reliable deployments, and make dependency management easy. Imagine an app you built 5 years ago using library.login: you should be able to trust that a new deployment will include up-to-date authentication code — because your app will be generated using the standard configuration, it is always backwards compatible. Even if massive high level changes need to be made, as long as you use &lt;code class=&quot;language-text&quot;&gt;library.login.isUserloggedIn&lt;/code&gt;, your view will be protected on a higher level by the generated app code.&lt;/p&gt;
&lt;h2&gt;🎬 Conclusion&lt;/h2&gt;
&lt;p&gt;I know I am being incredibly idealistic here. I’m certain that there are edge cases and complications that make this completely impractical for some purposes. But this isn’t supposed to be a panacea. It’s supposed to work for the 90% of applications that need to make CRUD operations protected by user authentication.&lt;/p&gt;
&lt;p&gt;But it’s fun to dream.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mapping out my side projects]]></title><description><![CDATA[It turns out there’s a lot to learn by examining the cadence and variety of your side-projects… Figure 1: Projects over time. Check out the…]]></description><link>https://blog.aidanbreen.com//mapping-out-my-side-projects/</link><guid isPermaLink="false">https://blog.aidanbreen.com//mapping-out-my-side-projects/</guid><pubDate>Fri, 07 Dec 2018 12:30:37 GMT</pubDate><content:encoded>&lt;p&gt;It turns out there’s a lot to learn by examining the cadence and variety of your side-projects…&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e397273e05132c62d42188dd0130165c/506ed/screenshot1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 28.759894459102902%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABMklEQVQY01WQ606DQBCFef8XM7FG4w3oymWBLdClVNtCubafg9GoP76cnZlk58xxbBawiZ553wRYYbdgfMrM5VCGvBce5ypkn/s0NuBYKj4Kn3bpiZ6riFpmp21Aa0OcoWuZhLFraI5H6ipjv1XsCoXy7gmCNXHsk+o1RpZn6cKb1J7gk4ia1GcjZImHM44TwzfjNDHPV6YLTKLNeSSvB3K7R6lHnp9u8L0Vav2AqyL8QJMkmtzElEXKxujlw5G/DMOiwxfTNHK9yKK+5dw27G1BZUJOcoVJDO6rIgpiwqgQdzmFKXG6ruOHvu9o2p7/vV4YxPnMFRDzzBe5Yr7QyaztBsyu4TGyrIsWx1rLX6rq913XNUfJ9XA4oLWWs5XkGaMXdCz5adI0obIlrvvC6u6WT8BawNoQQrjFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Figure 1: Projects over time.&quot;
        title=&quot;&quot;
        src=&quot;/static/e397273e05132c62d42188dd0130165c/40fad/screenshot1.png&quot;
        srcset=&quot;/static/e397273e05132c62d42188dd0130165c/707e9/screenshot1.png 148w,
/static/e397273e05132c62d42188dd0130165c/649e0/screenshot1.png 295w,
/static/e397273e05132c62d42188dd0130165c/40fad/screenshot1.png 590w,
/static/e397273e05132c62d42188dd0130165c/b3fef/screenshot1.png 885w,
/static/e397273e05132c62d42188dd0130165c/301c0/screenshot1.png 1180w,
/static/e397273e05132c62d42188dd0130165c/506ed/screenshot1.png 1516w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 1: Projects over time. Check out the original &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1SAovFyvcWUSptHiIF_4OrJ-IUCxL6fPWg1O7IWKYkrI/edit?usp=sharing&quot;&gt;spreadsheet here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I recently defended my PhD and in the process of preparing for the defence, I discovered that an old backup hard-drive was damaged beyond repair. Thankfully I had a further backup, but it got me thinking:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There’s a lot of stuff here that I probably will never look at again, but it’s important to me that I recognise it’s existence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I hope I never have to look back at the PHP I wrote when I was learning, but I spent months cobbling together SQL queries and designing objectively terrible pages, and that process is important, at least to me.&lt;/p&gt;
&lt;p&gt;Then I thought:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ve shared a lot of this stuff over the years, I wonder how many people have seen the stuff I’ve made?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The projects I could find hard data for include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Original Music&lt;/li&gt;
&lt;li&gt;6 Websites&lt;/li&gt;
&lt;li&gt;3 Youtube Channels&lt;/li&gt;
&lt;li&gt;3 Chrome Extensions&lt;/li&gt;
&lt;li&gt;2 SAAS platforms&lt;/li&gt;
&lt;li&gt;A physical product&lt;/li&gt;
&lt;li&gt;and this blog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3e3ca8c13b3be2dec953358d28a37f8a/09ba5/screenshot2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 38.31858407079646%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABh0lEQVQoz0VR2U7CUBDt/3+ETz76YmJQ32gi0cQFKWKhlJaUQuF2odCydPPImaQ6ycksuXPumRltMe4jCxxkq9nFz5DGIfLDAUmSwPd9geM4WC6XEsdxjDRNxbPGuCgKnE4ngdbrXMF+vcVqeI+lcQfLmiBJc1TlGYvFQhpI7rqu5Gxqa1VVIcsylGUpqOsa2r3ex/tEoT+N8GEpGHaEMD3jXP5gvQ6w3++x3W5h2zY2m400kYyg7XY7IaWnUq33dAdj0MXMfsPceYfl+tgfKjRNgyAI5CEVzedzydlEMo7cElI1TRQ+P1zD0G/gvHXg9TswTQsqLYCfBp7niUI2c2TuLM9zIWkJWeM71ihC6+ldjEcjmMMhvo0BxlMP6+SMoqxlxDAMZaQoiiQ/XA52PB5FNY0xVf/t8LGr4+XzE6OZje8LDFthFReo6kaOwP1F0b9CjscP2h3S8w3VCaH+YWLgKnx5yQUxJn6GqX9EfqoQhQoqzrAKcwxNB0opIWmPQCNZOy4JfwFq8VVeBwusBAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Figure 2: Project data.&quot;
        title=&quot;&quot;
        src=&quot;/static/3e3ca8c13b3be2dec953358d28a37f8a/40fad/screenshot2.png&quot;
        srcset=&quot;/static/3e3ca8c13b3be2dec953358d28a37f8a/707e9/screenshot2.png 148w,
/static/3e3ca8c13b3be2dec953358d28a37f8a/649e0/screenshot2.png 295w,
/static/3e3ca8c13b3be2dec953358d28a37f8a/40fad/screenshot2.png 590w,
/static/3e3ca8c13b3be2dec953358d28a37f8a/b3fef/screenshot2.png 885w,
/static/3e3ca8c13b3be2dec953358d28a37f8a/301c0/screenshot2.png 1180w,
/static/3e3ca8c13b3be2dec953358d28a37f8a/b5a53/screenshot2.png 1770w,
/static/3e3ca8c13b3be2dec953358d28a37f8a/09ba5/screenshot2.png 2260w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 2: Project data. Again, from the &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1SAovFyvcWUSptHiIF_4OrJ-IUCxL6fPWg1O7IWKYkrI/edit?usp=sharing&quot;&gt;spreadsheet here&lt;/a&gt; here. Links included, where possible&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Findings&lt;/h2&gt;
&lt;p&gt;I decided to go back as far as 2010, around about when I started my undergraduate degree, and when I started creating meaningful things that could be shared on the internet.&lt;/p&gt;
&lt;h3&gt;Relatively steady cadence&lt;/h3&gt;
&lt;p&gt;Figure 1 above shows pretty clearly that from 2012, I tended to start between 2 and 4 projects a year. There are some gaps (June 2015 — August 2016), and some times where I start a few projects close together (Early 2018), but overall, there’s never really a time where I don’t have a project to work on.&lt;/p&gt;
&lt;h3&gt;Blog posts come in spurts&lt;/h3&gt;
&lt;p&gt;The black bordered cells in Figure one indicate that at least 1 blog post was made that month. They tend to happen together, with large gaps in between. Obviously, I’ve begun to write more frequently lately. I think I also tend to write when I’m in need of a quick win: it’s a goal I can achieve in about a day or less and consider complete.&lt;/p&gt;
&lt;h3&gt;Newer projects are less viral&lt;/h3&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d319fc9a4c07018c0fb1dac6da2b80e8/bf197/screenshot3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 61.79401993355482%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABqUlEQVQoz5VT22oUQRCdr/dNETGgQnzQD9AXjUpEQQTB10AQiZvNzCbZ7Fx2btuXqe6TqtrMjojBWHA41RdOna7uTvq+R1VVWK/XqOtaeRgGeD8o/y8SEVwul2jbFpILhxAARMQYNY8xKN+GaV9E4r2/EZhCFo4XBDfEnfBdoYLijIh2FYkCHr+3qDpiQR6HLUL4t1MVHF2GsK0igk8/WtSb6ejC4/rfsBN0zqnD0dnIz1jwqiYcpQOMC1g1dKdeqkO5HQpx7KDm+58csiJg/7NDyvycWeI2l+O8OmzaTnv17afHZUV49d3h/huLNCe8+OIwuyI8ObRca3Ij+2WsjG1rBIkfSCsvSsLDA4O9D4bZ4sFbw64sHr0zeMmi914bHB55zFeErz/4nVJE3Qc03OeyCzhZkrYmkeNKhfmiwCljfl5xvlL8SnOcnct8idMs57kc2UWJWSbMuCyQXuS6d5YVcJ6QyHMxxqBr+ZdUBUzfwmw6dF2DnlEWK2XvDAZvEWhAVea8p0dTr2HNBm1T65qEOhyv3avbKcafs302vz18hrVWjfz5Ia4BXzie6/1jF7UAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Figure 3&quot;
        title=&quot;&quot;
        src=&quot;/static/d319fc9a4c07018c0fb1dac6da2b80e8/40fad/screenshot3.png&quot;
        srcset=&quot;/static/d319fc9a4c07018c0fb1dac6da2b80e8/707e9/screenshot3.png 148w,
/static/d319fc9a4c07018c0fb1dac6da2b80e8/649e0/screenshot3.png 295w,
/static/d319fc9a4c07018c0fb1dac6da2b80e8/40fad/screenshot3.png 590w,
/static/d319fc9a4c07018c0fb1dac6da2b80e8/b3fef/screenshot3.png 885w,
/static/d319fc9a4c07018c0fb1dac6da2b80e8/301c0/screenshot3.png 1180w,
/static/d319fc9a4c07018c0fb1dac6da2b80e8/bf197/screenshot3.png 1204w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Left to right: oldest to newest. We should disregard Medium (where this post was originally published) here as new posts were added over time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Obviously, older projects are going to have more views, but we would expect the decrease in lifetime views to be somewhat linear. In actual fact, my newer project don’t seem to get anything like the kind of exposure that my older projects got.&lt;/p&gt;
&lt;p&gt;I think this might be due to my focus changing from &lt;strong&gt;viral content&lt;/strong&gt; (“&lt;a href=&quot;http://blogify.org/&quot;&gt;blogify 2&lt;/a&gt;” was designed to copy content from reddit and repost the links), to actual &lt;strong&gt;useful tools&lt;/strong&gt; (“&lt;a href=&quot;http://bluespots.app/&quot;&gt;Blue Spots Parking&lt;/a&gt;” is an app to help disabled drivers find accessible parking spaces). Making useful tools that also gain viral traction is a sweet spot I have not yet managed to find.&lt;/p&gt;
&lt;p&gt;But perhaps this is also a reflection on how social networks are becoming less effective. Reddit, Facebook and twitter have always been the main way I share my projects.&lt;/p&gt;
&lt;h3&gt;Views are not correlated to revenue.&lt;/h3&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a8b10e7ab2d3f09b74894d4885d2f5d5/97886/screenshot4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 56.95142378559465%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABnklEQVQoz5VS20oDMRDdf/VT/AQRwWdRQURfRfBJ64WKongparVq1S3tbjax22ZzOWYyW1tfRAeyM2QnZ87MmaSqKuR5DikliqKIvixL/MecB7znODHGREACGw6HUEpFQOfcn461jiDjcQE5IVQf4P2kRG2Tu98OAZDddC3eMscM6UNt0/GzFf2UhbHsvf/JrjKU67HW0Di8q2KcTMCstbCxosd+q0I7tTG2jh8RmDY/QY3lf5vHGs0HwwxpdqPRiAEtA67sa5y2TYzp4UEokAqL9cb4+26W4XZT4+S+BhRCRBFIHFO3u3Gkcf5k6kE7LOyM8ZgaLO2O4x2NZZJ7924wvzXCRYcJJKQqtcxCsCCrjQpnT+5boMVdjU7fRz+7KmR7VxZzyyWuuzOikNHKRLZDhdvnAi8fAkqKuE7XHYV0IHHZLpALGXeVcmX4//IhcdJSeE0FnDUMSO1SQpqmGGQZPpVAnvVj3Ov1oIosPC4wLnnpKY92N8vykBv2Vw6Cl1OGLIiN4vB68B5qrUOhPPh6JHU3RIDyqSt+Px3PF27TUvl2UYAyAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Figure 4&quot;
        title=&quot;&quot;
        src=&quot;/static/a8b10e7ab2d3f09b74894d4885d2f5d5/40fad/screenshot4.png&quot;
        srcset=&quot;/static/a8b10e7ab2d3f09b74894d4885d2f5d5/707e9/screenshot4.png 148w,
/static/a8b10e7ab2d3f09b74894d4885d2f5d5/649e0/screenshot4.png 295w,
/static/a8b10e7ab2d3f09b74894d4885d2f5d5/40fad/screenshot4.png 590w,
/static/a8b10e7ab2d3f09b74894d4885d2f5d5/b3fef/screenshot4.png 885w,
/static/a8b10e7ab2d3f09b74894d4885d2f5d5/301c0/screenshot4.png 1180w,
/static/a8b10e7ab2d3f09b74894d4885d2f5d5/97886/screenshot4.png 1194w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;em&gt;These figures include prize money. “Reviewyour.Website”, “The Cub” and “Posture” have not received any prize money.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It’s safe to say that I won’t be quitting the day job any time soon, but I think making any money at all off a side-project is a massive success regardless of the magnitude. I have three main observations here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The project with the most views still made no money. I actually didn’t even put ads on the “Blogify 2” site. I felt it was immoral to profit off it.&lt;/li&gt;
&lt;li&gt;The physical product performed by far the best in terms of dollars-per-view. It was also the most challenging project though and probably took far more effort-per-view than any other project, if you could measure that.&lt;/li&gt;
&lt;li&gt;Music is hard.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It’s probably about time to start something new. If I want to maximize the views, revenue and longevity, I should probably try to build a physical product that is a useful tool with a viral aspect, which has nothing to do with music.&lt;/p&gt;
&lt;p&gt;Easy.&lt;/p&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/790790788ac7be90a8b468621b3fb6ff/a8a50/image1.jpeg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 56.25%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAABUZUtTMIC/8QAGxAAAQQDAAAAAAAAAAAAAAAAAQIEMTIDETP/2gAIAQEAAQUCbUJGsnRtBld//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFxEAAwEAAAAAAAAAAAAAAAAAARARIf/aAAgBAgEBPwE3F//EABgQAQEAAwAAAAAAAAAAAAAAAAABAhAh/9oACAEBAAY/AnF1kr//xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhMRCBobH/2gAIAQEAAT8hRQyNuZslv2NVd3NffC9E/9oADAMBAAIAAwAAABA0P//EABgRAAIDAAAAAAAAAAAAAAAAAAABESEx/9oACAEDAQE/EKUiw//EABgRAAIDAAAAAAAAAAAAAAAAAAABITFR/9oACAECAQE/EEaGjs//xAAdEAEBAAIBBQAAAAAAAAAAAAABEQAhQTFhocHw/9oACAEBAAE/EEWAaGTphFaqnPzbgqRbmeT644RxZ2x1Xbj/2Q==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;It&apos;s a Simpsons reference.&quot;
        title=&quot;&quot;
        src=&quot;/static/790790788ac7be90a8b468621b3fb6ff/f8fb9/image1.jpeg&quot;
        srcset=&quot;/static/790790788ac7be90a8b468621b3fb6ff/e8976/image1.jpeg 148w,
/static/790790788ac7be90a8b468621b3fb6ff/63df2/image1.jpeg 295w,
/static/790790788ac7be90a8b468621b3fb6ff/f8fb9/image1.jpeg 590w,
/static/790790788ac7be90a8b468621b3fb6ff/85e3d/image1.jpeg 885w,
/static/790790788ac7be90a8b468621b3fb6ff/d1924/image1.jpeg 1180w,
/static/790790788ac7be90a8b468621b3fb6ff/a8a50/image1.jpeg 1280w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;</content:encoded></item><item><title><![CDATA[Zero Dependency HTML templating in 12 lines of Javascript]]></title><description><![CDATA[This is a piece of code I wrote for a previous blog post about writing a Custom Mocha Reporter. I wanted to generate HTML output from a…]]></description><link>https://blog.aidanbreen.com//zero-dep-templating/</link><guid isPermaLink="false">https://blog.aidanbreen.com//zero-dep-templating/</guid><pubDate>Mon, 12 Nov 2018 12:30:37 GMT</pubDate><content:encoded>&lt;p&gt;This is a piece of code I wrote for a previous blog post about writing a Custom Mocha Reporter. I wanted to generate HTML output from a template, but I didn’t need much functionality or an extra dependency. Turns out, it’s not that difficult.&lt;/p&gt;
&lt;p&gt;Programmatically generating or producing HTML is not rocket science. The typical approach is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build a HTML template close to what you want.&lt;/li&gt;
&lt;li&gt;Identify the variable parts of the template you want to change.&lt;/li&gt;
&lt;li&gt;Write your code to get the replacement parts.&lt;/li&gt;
&lt;li&gt;Use a templating engine to load the HTML and replace the variables with the replacement parts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are loads of templating engines out there. &lt;a href=&quot;https://www.npmjs.com/package/mustache&quot;&gt;Mustache&lt;/a&gt; is one. &lt;a href=&quot;http://tryhandlebarsjs.com/&quot;&gt;Handlebars&lt;/a&gt;, &lt;a href=&quot;http://olado.github.io/doT/index.html&quot;&gt;doT&lt;/a&gt; and &lt;a href=&quot;http://ejs.co/&quot;&gt;EJS&lt;/a&gt; also do the job. They all basically work the same, but they all represent an extra dependency that we don’t need. We’re not doing anything complex here, and I’d rather keep this package with zero dependencies. So build it ourselves.&lt;/p&gt;
&lt;h2&gt;Regular Expressions&lt;/h2&gt;
&lt;p&gt;
  &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/62d91f4507912c727b85d8883bd8873c/73742/img1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
  
  &lt;span
    class=&quot;gatsby-resp-image-wrapper&quot;
    style=&quot;position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;&quot;
  &gt;
    &lt;span
      class=&quot;gatsby-resp-image-background-image&quot;
      style=&quot;padding-bottom: 33.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsSAAALEgHS3X78AAABYElEQVQY0yWQS07DQAyGcysWXIclW1jBERDcoKhiweMAlWBD1QoErWBBaSuaJm2ezSSZTGbymMwrjBLLsn7J/mX7M/KcUso5l7QRUqqibKqat20rlZKq7aNiFaaYScZEwyXjkles1NpYrRPXJxEoE0izvNnYKAixNqjOuYgJl62XO2b6B0o9FeEmT2pgwg2soBH4WYlL14HwAH374PyaCNVCqdE2mXrZyWj5bKdlkwAMQAbs2EIkM/1NnIOCEcP5Xiwe7t3JePn0GHy87cavGar1ztOX9fHd7Gj4eTE29ROT9+n52fnwdjgYDK6vbuZfc6qosfpxoRcmMUFZFcfEMkF/dlhQC9WXk+0sJIQmZmzmGHmpA3HqZe4e7mCVGhBRRPjWRoeo8ANs7VAECiFkj4o0QldQRPrItE72yIY0dfO9gzqz5sy5aDrUOrVNV01Ldcx63pqzbjLBeqFTKI1F/QPTKH8iCGL4ugAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
    &gt;
      &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        style=&quot;width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;&quot;
        alt=&quot;Figure 1: word replacement.&quot;
        title=&quot;&quot;
        src=&quot;/static/62d91f4507912c727b85d8883bd8873c/40fad/img1.png&quot;
        srcset=&quot;/static/62d91f4507912c727b85d8883bd8873c/707e9/img1.png 148w,
/static/62d91f4507912c727b85d8883bd8873c/649e0/img1.png 295w,
/static/62d91f4507912c727b85d8883bd8873c/40fad/img1.png 590w,
/static/62d91f4507912c727b85d8883bd8873c/73742/img1.png 600w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
      /&gt;
    &lt;/span&gt;
  &lt;/span&gt;
  
  &lt;/a&gt;
    &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Artists impression of a lake surrounded by red cabbage and carrots fields.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Regular expressions (regex) are a &lt;strong&gt;wonderful, powerful and complex&lt;/strong&gt; concept that I wont get into in too much detail. Basically, a regular expression allows you to define an “expression”, or pattern, that can be used to find specific sequences in a body of text. A common use of regex is to find a substring, and replace it. Regex can also be used to validate data, ensuring it conforms to some structure (ie. phone numbers contain only numbers and are 10 digits long)&lt;/p&gt;
&lt;h2&gt;Replacing a String.&lt;/h2&gt;
&lt;p&gt;Regex are not unique to Javascript, and even within JS, there are multiple ways to utilise them. In our case, we’ll use the &lt;code class=&quot;language-text&quot;&gt;string.replace()&lt;/code&gt; method. (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace&quot;&gt;full documentation here&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;str.replace(regexp|substr, newSubstr|function)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re going to take a string called &lt;code class=&quot;language-text&quot;&gt;template&lt;/code&gt; and find every occurrence of words surrounded by double squiggly brackets like so: &lt;code class=&quot;language-text&quot;&gt;{{word}}&lt;/code&gt; . Then we’re going to replace that word with a value we have generated — in the case of the Mocha reporter, we used the output of our tests, but it can be anything at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For example&lt;/strong&gt;, say we have calculated the percent of tests that ran successfully as: run&lt;em&gt;percent , and in our template we have written `&lt;code class=&quot;language-text&quot;&gt;&lt;/code&gt;{{run&lt;/em&gt;percent}}`&lt;code class=&quot;language-text&quot;&gt;&lt;/code&gt;. We could write:  &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;template.replace(/{{run_precent}}/g, run_percent)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Creating_a_regular_expression&quot;&gt;regex literal&lt;/a&gt; is surrounded by forward slashes. The ‘g’ means &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global&quot;&gt;search globally&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Replacing Multiple Strings&lt;/h2&gt;
&lt;p&gt;But we can do better. We can find any word surrounded by squigglies with the following regex: &lt;code class=&quot;language-text&quot;&gt;/{{.*?}}/g&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.&lt;/code&gt; means any character except a newline.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt; means match the previous thing 0 or more times.&lt;br&gt;
&lt;code class=&quot;language-text&quot;&gt;?&lt;/code&gt; means make the previous thing ‘non-greedy’ — ie. The shortest possible match.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, we’re not just going to replace every word in squigglies with just one value. If we pass a function as the second parameter to &lt;code class=&quot;language-text&quot;&gt;replace()&lt;/code&gt;, we can be much smarter…&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; newContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; template&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;/{{.*?}}/g&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; replacements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here,Match is the word that is found by the regex. replacements is an object with keys for each of our template variables (the squigglies), and values for what we want to replace them with. Here’s an example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Example of replacements object&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;{{lastrun_date}}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;{{run_percent}}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; run_percent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;{{run_numerator}}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sum&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;{{run_denominator}}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; total
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Wrapping it all up with the file reading/writing code in a nice little function (called updateBillboard in our reporter) and we have our templating engine in &lt;a href=&quot;https://github.com/aido179/apb-mocha-reporter/blob/140e22119925142dd2d09bd22dd62764bbd46799/src/apbmochareporter.js#L91&quot;&gt;12 lines of Javascript&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateBillboard&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;replacements&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
  content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`template.html`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;encoding&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; newContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;/{{.*?}}/g&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; replacements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`/output.html`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mkdirSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newContent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; output
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Obviously, this isn’t going to be suitable for anything with complex templating needs. But, in my opinion, installing a whole extra package just to replace a few values in a file is overkill.&lt;/p&gt;</content:encoded></item></channel></rss>