stedman.dev The @stedman home range. 2022-12-08T02:30:13Z https://stedman.dev/ Steve Stedman Austin JavaScript Case Study https://stedman.dev/2020/07/23/austin-javascript-case-study/ 2022-12-08T02:30:13Z 2022-12-08T02:30:13Z <p><a href="https://austinjavascript.com/">Austin JavaScript</a> (AustinJS) is a tech meetup organization with a proud history of involvement from local hackers to JavaScript industry giants. For over ten years, it’s provided timely and relevant JavaScript knowledge while serving as a key social network for the Austin web development community. If it has <em>anything</em> to do with JavaScript, it’s being talked about at AustinJS.</p> <p>So it was always a bit ironic that JavaScript as a technology didn’t play a bigger role in the AustinJS website. Granted, a key consideration was performance on mobile devices and client-side JavaScript can degrade rendering speeds and battery life if not crafted properly. But even on the server-side, there wasn’t a contribution model in the <a href="https://nodejs.org/">Node</a> world that quite fit AustinJS’s needs. This volunteer organization’s content management system needed to be simple, deployments needed to be effortless, and whatever was chosen needed to be free.</p> <figure class="image browser-window"> <img src="https://stedman.dev/assets/img/posts/austinjs-2013.png" alt="Austin JavaScript (WordPress), 2013" /> <figcaption>AustinJavaScript.com, circa 2013</figcaption> </figure> <p>The site was initially set up in 2010 on <a href="https://wordpress.org/">WordPress</a> (PHP) and then moved to <a href="https://pages.github.com/">GitHub Pages</a> and <a href="https://jekyllrb.com/">Jekyll</a> (Ruby) in 2015. As a publishing platform, the GitHub Pages + Jekyll duo is a hard combination to beat. It’s rock-solid, easy to update (even with a mobile device), deployments are invisible, and it’s free.</p> <p>But it’s not JavaScript. Worse, Jekyll runs on Ruby which is a challenging ecosystem for the uninitiated to set up locally. This impeded layout and design contributions, so not much evolved on the site beyond 2015.</p> <figure class="image browser-window"> <img src="https://stedman.dev/assets/img/posts/austinjs-2016.png" alt="Austin JavaScript (Jekyll), 2016" /> <figcaption>AustinJavaScript.com, circa 2016</figcaption> </figure> <p>Fast-forward to 2020. The JavaScript community now has a wide variety of capable Jekyll alternatives. Better yet, the automated build and deploy process that Jekyll enjoys on GitHub Pages can be replicated with the recently released <a href="https://github.com/features/actions">GitHub Actions</a>.</p> <p>It was time to rebuild AustinJS on JavaScript.</p> <blockquote> <p>On this page: <a href="https://stedman.dev/2020/07/23/austin-javascript-case-study/#1-the-prep">The prep</a> &gt; <a href="https://stedman.dev/2020/07/23/austin-javascript-case-study/#2-the-pitch">The pitch</a> &gt; <a href="https://stedman.dev/2020/07/23/austin-javascript-case-study/#3-the-pivot">The pivot</a> &gt; <a href="https://stedman.dev/2020/07/23/austin-javascript-case-study/#4-the-launch">The launch</a> &gt; <a href="https://stedman.dev/2020/07/23/austin-javascript-case-study/#conclusion">Conclusion</a></p> </blockquote> <h2 id="1.-the-prep" tabindex="-1">1. The prep </h2> <p>Before approaching the AustinJS organizers, I wanted to have a decent proof of concept to share. I figured I’d push some of the current content through a JavaScript engine, throw a fresh coat of paint on it, and see what folks thought.</p> <h3 id="discovery" tabindex="-1">Discovery </h3> <p>Before starting, I put on my project management hat and did some fieldwork. Their GitHub repo had been up for five years, so there was plenty of material to research:</p> <ol> <li>Content: patterns of use (and evolutions in those patterns)</li> <li>GitHub Pull Requests: issues that need to be addressed by code change</li> <li>GitHub Issues: issues that need to be addressed</li> <li>Templates/other code: comments, FIXMEs, and TODOs</li> </ol> <h3 id="goals" tabindex="-1">Goals </h3> <p>After the research, my initial goals were simple:</p> <ol> <li>maintain content parity (while the engine and the design might change, the content should remain the same)</li> <li>keep all of Jekyll’s ease of use (e.g., YAML front matter for meta data, Markdown content, straightforward templating)</li> <li>keep the lightweight and responsive static pages</li> <li>keep GitHub Pages’ auto build and deploy functionality</li> <li>improve the contributor experience (e.g., meetup posts, site templates)</li> <li>improve the user experience with a visual design refresh</li> <li>reduce dependencies (especially those requiring additional services and logins; Travis and Cloudflare, for example)</li> </ol> <h3 id="proof-of-concept" tabindex="-1">Proof of concept </h3> <h4>Generator</h4> <p><a href="https://gatsbyjs.com/">Gatsby</a> seemed like a solid JavaScript static site generator (SSG) choice. Based on ReactJS, it has tons of documentation, a good Jekyll migration path, and a supportive community.</p> <h4>Style</h4> <p>I also decided to give the CSS framework <a href="https://bulma.io/">Bulma</a> a shot as it is lightweight, flexible, and well-documented. For this community project, I figured that it’s also important to make design contribution as frictionless as possible. Bulma impressed me as hitting that sweet spot of capability and ease of use.</p> <h4>Content</h4> <p>Keeping Jekyll’s YAML front matter + Markdown content layout was never a question. It’s a tried and true system that’s been adopted by all the Jekyll alternatives. The home page, about page, etc. migrated without a hitch.</p> <p>I took a closer look at the 100+ meetup posts from the past ten years. Details such as speaker names, bios, avatars, sponsor names, and venue locations in the Markdown could be standardized and moved up into the YAML front matter. This front matter data can then be used in the current page or elsewhere on the site. Standardizing the data format also makes linting and/or pre-flight field checks possible before allowing pull requests to be opened.</p> <p>Thus, a <a href="https://github.com/austinjavascript/austinjavascript.com/blob/v2-eol/_posts/2020-02-16-meetup.md">typical original meetup (<code>2020-02-16-meetup.md</code>)</a> file:</p> <pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">---</span><br /><span class="token key atrule">title</span><span class="token punctuation">:</span> Absolute Unit Tests with Jest and Enzyme<br /><span class="token key atrule">author</span><span class="token punctuation">:</span> kevin<br /><span class="token key atrule">layout</span><span class="token punctuation">:</span> post<br /><span class="token key atrule">categories</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> posts<br /> <span class="token punctuation">-</span> meetups<br /><span class="token key atrule">when</span><span class="token punctuation">:</span> <span class="token datetime number">2020-02-18T19:30:00-06:00</span><br /><span class="token punctuation">---</span><br /><br /><span class="token punctuation">{</span>% assign speakr = 'Nick Gottschlich' %<span class="token punctuation">}</span><br /><span class="token punctuation">{</span>% assign twiturl = 'https<span class="token punctuation">:</span>//twitter.com/nickgottschlich/' %<span class="token punctuation">}</span><br /><span class="token punctuation">{</span>% assign huburl = 'https<span class="token punctuation">:</span>//github.com/nick<span class="token punctuation">-</span>gottschlich/' %<span class="token punctuation">}</span><br /><span class="token punctuation">{</span>% assign website = 'http<span class="token punctuation">:</span>//nickpgott.com' %<span class="token punctuation">}</span><br /><br /><br />Everything you wanted to know about testing React components with Jest and Enzyme<span class="token tag">!</span> Learn what a unit test is<span class="token punctuation">,</span> why its useful<span class="token punctuation">,</span> <span class="token key atrule">and how to test things like</span><span class="token punctuation">:</span> existence of react components<span class="token punctuation">,</span> simulated clicks and other events<span class="token punctuation">,</span> mocking data<span class="token punctuation">,</span> snapshots and more<span class="token tag">!</span><br /><br /><span class="token comment">### Our speaker</span><br /><br />&lt;div class="media<span class="token punctuation">-</span>object speaker<span class="token punctuation">-</span>bio"<span class="token punctuation">></span><br /> &lt;a href="<span class="token punctuation">{</span><span class="token punctuation">{</span> twiturl <span class="token punctuation">}</span><span class="token punctuation">}</span>"<span class="token punctuation">></span><br /> &lt;img alt="<span class="token punctuation">{</span><span class="token punctuation">{</span> speakr <span class="token punctuation">}</span><span class="token punctuation">}</span> @NickGottschlich on Twitter"<br /> src="https<span class="token punctuation">:</span>//pbs.twimg.com/profile_images/1029847332781740032/Gp54dk3Z_400x400.jpg" /<span class="token punctuation">></span><br /> &lt;/a<span class="token punctuation">></span><br /> &lt;div<span class="token punctuation">></span><br /> &lt;a href="<span class="token punctuation">{</span><span class="token punctuation">{</span> twiturl <span class="token punctuation">}</span><span class="token punctuation">}</span>"<span class="token punctuation">></span>&lt;strong<span class="token punctuation">></span><span class="token punctuation">{</span><span class="token punctuation">{</span> speakr <span class="token punctuation">}</span><span class="token punctuation">}</span>&lt;/strong<span class="token punctuation">></span>&lt;/a<span class="token punctuation">></span><span class="token punctuation">,</span> Software Engineer at Procore Technologies<br /> &lt;p<span class="token punctuation">></span><br /> <span class="token key atrule">Twitter</span><span class="token punctuation">:</span> &lt;a href="<span class="token punctuation">{</span><span class="token punctuation">{</span> twiturl <span class="token punctuation">}</span><span class="token punctuation">}</span>"<span class="token punctuation">></span>@NickGottschlich&lt;/a<span class="token punctuation">></span><br /> &lt;/p<span class="token punctuation">></span><br /> &lt;p<span class="token punctuation">></span><br /> <span class="token key atrule">Website</span><span class="token punctuation">:</span> &lt;a href="<span class="token punctuation">{</span><span class="token punctuation">{</span> website <span class="token punctuation">}</span><span class="token punctuation">}</span>"<span class="token punctuation">></span>http<span class="token punctuation">:</span>//nickpgott.com/&lt;/a<span class="token punctuation">></span><br /> &lt;/p<span class="token punctuation">></span><br /> &lt;p<span class="token punctuation">></span><br /> <span class="token key atrule">Github</span><span class="token punctuation">:</span> &lt;a href="<span class="token punctuation">{</span><span class="token punctuation">{</span> huburl <span class="token punctuation">}</span><span class="token punctuation">}</span>"<span class="token punctuation">></span>nick<span class="token punctuation">-</span>gottschlich&lt;/a<span class="token punctuation">></span><br /> &lt;/p<span class="token punctuation">></span><br /> &lt;/div<span class="token punctuation">></span><br />&lt;/div<span class="token punctuation">></span><br /><br />Make sure to thank our gracious host <span class="token punctuation">[</span>Cloudflare<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span>.<br /><br /><span class="token punctuation">{</span>% include give<span class="token punctuation">-</span>em<span class="token punctuation">-</span>the<span class="token punctuation">-</span>business.html location='cloudflare' %<span class="token punctuation">}</span><br /><br />Check back here or &lt;a href="<span class="token punctuation">{</span><span class="token punctuation">{</span> site.twitter.url <span class="token punctuation">}</span><span class="token punctuation">}</span>"<span class="token punctuation">></span>follow us on Twitter&lt;/a<span class="token punctuation">></span><br />for updates.<br /><br /><span class="token punctuation">[</span>cloudflare<span class="token punctuation">]</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//www.cloudflare.com/</code></pre> <p>…was transformed into the following concise post:</p> <pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">---</span><br /><span class="token key atrule">layout</span><span class="token punctuation">:</span> meetup<br /><span class="token key atrule">title</span><span class="token punctuation">:</span> Absolute Unit Tests with Jest and Enzyme<br /><span class="token key atrule">speakers</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Nick Gottschlich<br /> <span class="token key atrule">title</span><span class="token punctuation">:</span> Software Engineer at Procore Technologies<br /> <span class="token key atrule">avatar</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//pbs.twimg.com/profile_images/1029847332781740032/Gp54dk3Z_400x400.jpg<br /> <span class="token key atrule">bio</span><span class="token punctuation">:</span><br /> <span class="token key atrule">email</span><span class="token punctuation">:</span><br /> <span class="token key atrule">homepage</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//nickpgott.com<br /> <span class="token key atrule">twitter</span><span class="token punctuation">:</span> NickGottschlich<br /> <span class="token key atrule">github</span><span class="token punctuation">:</span> nick<span class="token punctuation">-</span>gottschlich<br /> <span class="token key atrule">linkedin</span><span class="token punctuation">:</span><br /><span class="token key atrule">sponsor</span><span class="token punctuation">:</span> cloudflare<br /><span class="token key atrule">venue</span><span class="token punctuation">:</span> cloudflare<br /><span class="token key atrule">after</span><span class="token punctuation">:</span> lavaca<br /><span class="token key atrule">organizers</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> lingram<br /> <span class="token punctuation">-</span> astacy<br /><span class="token punctuation">---</span><br /><br />Everything you wanted to know about testing React components with <span class="token punctuation">[</span>Jest<span class="token punctuation">]</span>(https<span class="token punctuation">:</span>//jestjs.io/) and <span class="token punctuation">[</span>Enzyme<span class="token punctuation">]</span>(https<span class="token punctuation">:</span>//enzymejs.github.io/enzyme/) Learn what a unit test is<span class="token punctuation">,</span> why its useful<span class="token punctuation">,</span> <span class="token key atrule">and how to test things like</span><span class="token punctuation">:</span> existence of react components<span class="token punctuation">,</span> simulated clicks and other events<span class="token punctuation">,</span> mocking data<span class="token punctuation">,</span> snapshots and more<span class="token tag">!</span></code></pre> <blockquote> <p>Note: To help contributors, I created a <a href="https://github.com/austinjavascript/austinjavascript.com/blob/master/CONTRIBUTING-POSTS.md">how to contribute to posts</a> document that provides more details about each field. The mechanics of how it all works is a bit more involved and would be a great topic for another blog post.</p> </blockquote> <p>You may have noticed that the <code>when</code> and <code>categories</code> fields were omitted in the new version above.</p> <h5>When</h5> <p>In Jekyll-land, the first part of the post file name (e.g., <code>2020-02-18-meetup.md</code>) contains the publish date. AustinJS organizers were unable to use future publish dates because Jekyll throws an error. So organizers added the <code>when</code> front matter field to represent the meetup date in the future. Unfortunately they also used it for the meeting <strong>time</strong> as well. While analyzing past pull requests, I noticed all sorts of Daylight Saving Time snafus that required subsequent PRs to remedy.</p> <p>Gatsby didn’t throw errors if file names had future dates so I was able to dispose of the <code>when</code> field. Oh, and since almost every one of the 100 or so meetups started at 7:30pm and ended at 9pm, I was able to hardwire that into the template as well.</p> <h5>Categories</h5> <p>The <code>categories</code> front matter field generates the site sub-folders—at least in the AustinJS Jekyll setup. Using our example from above, the output file path looks like:</p> <pre class="language-html"><code class="language-html">/<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>cat-1</span><span class="token punctuation">></span></span>/<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>cat-2</span><span class="token punctuation">></span></span>/<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>date</span><span class="token punctuation">></span></span>/ # format<br />/posts/meetups/2020/02/18/ # actual</code></pre> <p>With Gatsby, the permalink format is defined in the <code>gatsby-node.js</code> file, so the <code>categories</code> front matter fields are completely unnecessary.</p> <h2 id="2.-the-pitch" tabindex="-1">2. The pitch </h2> <p>After wrangling Gatsby, Bulma, and a sample of content for a few days, I soon had a <a href="https://github.com/austinjavascript/austinjavascript.com/issues/105">proof of concept</a> to share with AustinJS leadership.</p> <p>Aaron Stacey, long-time AustinJS organizer and developer of the second gen site, was pleased with the idea of it all but added that an MVP (minimal viable product) would include the following:</p> <ol> <li>Deploy on push</li> <li>Have a low page weight</li> <li>Look good on mobile</li> </ol> <p>Hooray! We agreed on goals. But then Aaron pointed out the page weight. My Gatsby-powered home page had ballooned to 30 times the size of the Jekyll-powered version. It now weighed-in at over 3GB. What the heck? Sure it was speedy on my desktop, but there were valid concerns about load times for mobile devices.</p> <p>Gatsby hydrates the initial page after the static bits load and subsequent page loads are routed through ReactJS. So, as you can imagine, you’re getting a large upfront JS payload followed by smaller GraphQL data transfers to navigate additional pages. For a site that typically only experiences one page view per session (to view details about an upcoming meetup), it’s important to maintain a trim initial page weight.</p> <p>Aaron kindly mentioned that <a href="https://11ty.dev/">Eleventy (11ty)</a> might be a better fit.</p> <h2 id="3.-the-pivot" tabindex="-1">3. The pivot </h2> <p>Changing horses from Gatsby to Eleventy was relatively uneventful. I quickly ported over the Bulma CSS and the posts with the newly standardized YAML front matter. Migrating the Gatsby guts was a bit more delicate but once on Eleventy, everything was gravy.</p> <blockquote> <p>Note: The <a href="https://liquidjs.com/">LiquidJS templating language</a> used by Eleventy is a port of the very same Liquid language that Jekyll uses. Direct migrations from Jekyll to Eleventy are relatively seamless (with a few exceptions, of course).</p> </blockquote> <p>Once I switched to Eleventy, things got even easier. Working with Eleventy is a joy. For the most part, things just make sense. The community is vibrant and ready to help when things don’t make so much sense.</p> <h3 id="github-actions" tabindex="-1">GitHub Actions </h3> <p>While a JavaScript SSG is pretty exciting, this site needed to build and deploy whenever the master branch was updated. Jekyll did this out of the box and it meant that organizers could update meetup details from their mobile devices and see the changes live on the site within minutes.</p> <p><a href="https://docs.github.com/en/actions">GitHub Actions</a> adds the ability to spin up a server to run builds, tests, deployments, etc. based on repo events such as pull requests or merges.</p> <p>Setting up the AustinJS workflow was absolutely painless (see <a href="https://stedman.dev/2020/04/29/make-the-jump-from-jekyll-to-javascript/">Make the Jump from Jekyll to JavaScript</a>) and has been running like a tank ever since.</p> <h3 id="enhancements" tabindex="-1">Enhancements </h3> <p>Once the basics were nailed down, there was time to add even more value.</p> <ul> <li><a href="https://austinjavascript.com/">home</a> page: add an Austin skyline hero (to make a place connection) and a roll-call of past speakers/presentations (to make a people connection)</li> <li><a href="https://austinjavascript.com/contributing/">contributing</a> page: add a page to help presenters, sponsors, hosts, and others get more involved</li> <li><a href="https://austinjavascript.com/posts/meetups/">meetup</a> posts: add videos and slide decks from past presentations</li> <li><a href="https://austinjavascript.com/feed.xml">RSS feed</a></li> <li>SSL/CI/CDN services: switch to GitHub and remove Cloudflare and Travis dependencies</li> </ul> <figure class="image browser-window"> <img src="https://stedman.dev/assets/img/posts/austinjs-2020.png" alt="Austin JavaScript, 2020" /> <figcaption>AustinJavaScript.com, circa 2020</figcaption> </figure> <h3 id="goals-review" tabindex="-1">Goals review </h3> <p>I reviewed the <a href="https://stedman.dev/2020/07/23/austin-javascript-case-study/#goals">goals</a> one last time:</p> <ol> <li>✅ maintain content parity</li> <li>✅ keep all of Jekyll’s ease of use</li> <li>✅ keep the lightweight and responsive static pages</li> <li>✅ keep GitHub Pages’ auto build and deploys</li> <li>✅ improve the contributor experience</li> <li>✅ improve the user experience</li> <li>✅ reduce dependencies</li> </ol> <p>With all the boxes checked, it was time to launch.</p> <h2 id="4.-the-launch" tabindex="-1">4. The Launch </h2> <p>This launch called for the development branch to be merged into the main branch and then setting up the Actions workflow to automate future build/deployments. Simple tasks, but replacing engines while the old one still works is always a scary move. I pressed the buttons and hoped for the best.</p> <p>I saw source code.</p> <p>All that showed up on the AustinJS site was source code for the index page. Was there something I missed in an Eleventy config? Was something wrong with the Actions workflow?</p> <p>Another hour of testing and research revealed that this repo didn’t have the same ability to deploy to <code>gh-pages</code> branch as my demo site. Huh? A bit <a href="https://help.github.com/en/github/working-with-github-pages/about-github-pages#publishing-sources-for-github-pages-sites">more research</a> revealed that <code>&lt;org_name&gt;.github.io</code> type repos can only deploy to <code>master</code>. There weren’t many options. Change the <code>austinjavascript.github.io</code> repo name or give up and return to Jekyll.</p> <p>I got on a short call with Aaron and went through the options. We decided to rename the repo to <code>austinjavascript.com</code> and break the logjam. A few short moments later, the new site was live.</p> <h2 id="conclusion" tabindex="-1">Conclusion </h2> <p>Since the launch, the site has run flawlessly. It builds a bit faster than the old Jekyll engine but you’d be hard-pressed to find any other deployment differences.</p> <p>I set out to improve the AustinJS user/contributor experience and bury the <a href="https://github.com/austinjavascript/austinjavascript.com/blob/v2-eol/README.md#previewing-changes">cruel Ruby joke</a>. With special thanks to Aaron Stacey and Kevin Kipp for their help, I believe I succeeded. At long last, Austin JavaScript can proudly say that, with regards to JavaScript, it is <a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">eating it’s own dog food</a>.</p> Watching Sass in Eleventy https://stedman.dev/2020/05/11/watching-sass-in-eleventy/ 2022-12-08T02:30:13Z 2022-12-08T02:30:13Z <p>I’ve been using <a href="https://github.com/sass/node-sass">node-sass</a> in my <a href="https://11ty.dev/">11ty</a> workflow for awhile now but haven’t been able to get the <code>--watch</code> flag to work to my satisfaction. For instance, while Browsersync automatically refreshes the browser for content and template changes, it didn’t rebuild my Sass files and then refresh the browser.</p> <p>Well, I finally had enough of the manual labor and found <a href="https://dev.to/mathieuhuot/processing-sass-with-11ty-5a09">a Mathieu Huot solution</a> that sorta worked for me. I made a few tweaks that I hope he doesn’t mind:</p> <ul> <li>I don’t have a lot of Sass files to process and my render times were in the 125ms range. So I chose the simplicity of a synchronous process instead of a promise-based one. Bonus: I get to skip a dependency (<code>fs-extra</code>).</li> <li>Rather than introduce an environment variable (e.g., <code>dev</code>) to toggle the start of this script (and prevent it running it in production), I look for the <code>--watch</code> process argument. It’s present when Eleventy is fired up in dev mode: <code>node eleventy --serve --watch</code>.</li> </ul> <blockquote> <p>UPDATE (2020-09-15): I recently came across an <a href="https://egghead.io/lessons/11ty-add-sass-compiling-and-watch-for-changes-in-eleventy-11ty">Egghead tutorial on Sass compiling</a> that may work better for you.</p> </blockquote> <h2 id="let%E2%80%99s-go" tabindex="-1">Let’s go </h2> <ol> <li> <p>Make sure <code>node-sass</code> is installed.</p> <pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> --save-dev node-sass</code></pre> </li> <li> <p>Open a new file, name it <code>_includes/sass-watch.js</code>, and drop in the following code.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">const</span> sass <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'node-sass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/**<br /> * Render and save the Sass to CSS.<br /> * @param {string} sassPath The Sass input path.<br /> * @param {string} cssFilePath The CSS output file path.<br /> */</span><br /><span class="token keyword">const</span> <span class="token function-variable function">buildCss</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">sassPath<span class="token punctuation">,</span> cssFilePath</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// Render CSS from Sass source path.</span><br /> <span class="token keyword">const</span> rendered <span class="token operator">=</span> sass<span class="token punctuation">.</span><span class="token function">renderSync</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">file</span><span class="token operator">:</span> sassPath <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Save CSS to output path.</span><br /> fs<span class="token punctuation">.</span><span class="token function">writeFile</span><span class="token punctuation">(</span>cssFilePath<span class="token punctuation">,</span> rendered<span class="token punctuation">.</span>css<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">writeErr</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>writeErr<span class="token punctuation">)</span> <span class="token keyword">throw</span> writeErr<span class="token punctuation">;</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">CSS file saved: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>cssFilePath<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> (in </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>rendered<span class="token punctuation">.</span>stats<span class="token punctuation">.</span>duration<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">ms)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token comment">/**<br /> * Initialize and watch Sass for changes requiring a build.<br /> * @param {string} sassPath The Sass input path.<br /> * @param {string} cssFilePath The CSS output file path.<br /> */</span><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">sassPath<span class="token punctuation">,</span> cssFilePath</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// If CSS output directory doesn't already exist, make it.</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fs<span class="token punctuation">.</span><span class="token function">existsSync</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>cssFilePath<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Creating new CSS directory: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>cssFilePath<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Create output directory.</span><br /> fs<span class="token punctuation">.</span><span class="token function">mkdir</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>cssFilePath<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">recursive</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">mkdirErr</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>mkdirErr<span class="token punctuation">)</span> <span class="token keyword">throw</span> mkdirErr<span class="token punctuation">;</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'CSS directory created.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token comment">// Build CSS on startup.</span><br /> <span class="token function">buildCss</span><span class="token punctuation">(</span>sassPath<span class="token punctuation">,</span> cssFilePath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Watch for changes to Sass directory.</span><br /> fs<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>sassPath<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">evType<span class="token punctuation">,</span> filename</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SCSS file changed: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>sassPath<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filename<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Rebuild the CSS.</span><br /> <span class="token function">buildCss</span><span class="token punctuation">(</span>sassPath<span class="token punctuation">,</span> cssFilePath<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> </li> <li> <p>Edit <code>.eleventy.js</code> so that it now includes the following <code>sass-watch</code> bits.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> sassWatch <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./_includes/sass-watch'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Run only when 11ty is in watch mode.</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>argv<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'--watch'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token comment">// Watch Sass directory for updates.</span><br /> <span class="token function">sassWatch</span><span class="token punctuation">(</span><span class="token string">'./src/_sass/_main.scss'</span><span class="token punctuation">,</span> <span class="token string">'./dist/css/main.css'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Refresh the browser when there are updates in the Sass directory.</span><br /> eleventyConfig<span class="token punctuation">.</span><span class="token function">addWatchTarget</span><span class="token punctuation">(</span><span class="token string">'./src/_sass/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">.</span><span class="token punctuation">.</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>The config for the command line and VSCode is no different from the standard setup:</p> <ul> <li> <p>Command line: <code>package.json</code> start script:</p> <pre class="language-json"><code class="language-json"><span class="token property">"start"</span><span class="token operator">:</span> <span class="token string">"eleventy --serve --watch"</span></code></pre> </li> <li> <p>VSCode: <code>.vscode/launch.json</code> script:</p> <pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"0.2.0"</span><span class="token punctuation">,</span><br /> <span class="token property">"configurations"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">{</span><br /> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"node"</span><span class="token punctuation">,</span><br /> <span class="token property">"request"</span><span class="token operator">:</span> <span class="token string">"launch"</span><span class="token punctuation">,</span><br /> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Launch 11ty"</span><span class="token punctuation">,</span><br /> <span class="token property">"cwd"</span><span class="token operator">:</span> <span class="token string">"${workspaceFolder}"</span><span class="token punctuation">,</span><br /> <span class="token property">"runtimeExecutable"</span><span class="token operator">:</span> <span class="token string">"npm"</span><span class="token punctuation">,</span><br /> <span class="token property">"runtimeArgs"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"start"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span></code></pre> </li> </ul> </li> <li> <p>Start Eleventy again via command line (<code>npm start</code>) or VSCode via the Debugger, all subsequent changes to Sass files should initiate an automatic build and the browser should refresh with the changes.</p> </li> </ol> <h2 id="conclusion" tabindex="-1">Conclusion </h2> <p>I sincerely hope there’s a better solution for kicking-off Sass builds on file change events. If you know of a better way, please reach out on <a href="https://twitter.com/stedman">Twitter @stedman</a>.</p> <p>Until then, I’ll be happily updating Sass and watching the changes show up in my browser.</p> Make the Jump from Jekyll to JavaScript https://stedman.dev/2020/04/29/make-the-jump-from-jekyll-to-javascript/ 2022-12-08T02:30:13Z 2022-12-08T02:30:13Z <p>With the recent addition of <a href="https://github.com/features/actions">Actions</a> to GitHub’s product lineup, we now have the ability to create a <em>100% Pure, Unfiltered™</em> JavaScript alternative to Jekyll (and Ruby) in GitHub Pages. This is a big deal. Really.</p> <h2 id="back-story" tabindex="-1">Back story </h2> <p><a href="https://pages.github.com/">GitHub Pages</a>, for the uninitiated, are public web pages automatically generated from your GitHub source files and freely hosted on <a href="http://github.io/">github.io</a> or your custom domain. There are no servers to provision and no databases to set up.</p> <p>The automatic generation part is handled by <a href="https://jekyllrb.com/">Jekyll</a>, a pretty sweet static site generator. It’s been around quite awhile, is battle tested, and is backed by a huge community. It’s also simple enough for beginners and yet powerful enough for advanced users. Unfortunately, customizing it requires running it locally and this is where things get sticky. Jekyll is based on Ruby. For casual users, Ruby is a challenge to set up and maintain—especially on Windows. Scaling is also an issue for Jekyll/Ruby as bigger builds are notorious for taking a long time to compile.</p> <p>While there are plenty of competitive static site generators out there, the one piece that was missing was the integrated of build and deploy to GitHub’s web server. Pages+Jekyll took care of that build and deploy mumbo-jumbo for you, automagically. Just commit to the <code>gh-pages</code> branch and <strong>poof</strong>, your site is updated.</p> <p>More recently, workarounds from <a href="https://netlify.com/">Netlify</a> and others have popped up. They handle build/deploy and offer additional features worth considering, but at the end of the day they are yet another dependency to maintain.</p> <p>If only we could run a static site generator on Pages with all the hooks and privileges of Jekyll but with the ease-of-setup and performance provided by more modern alternatives.</p> <p>Well, now you can. All the pieces have finally fallen into place.</p> <blockquote> <p><strong>UPDATE:</strong> If you’re working with GitHub <strong>user</strong> or <strong>organization</strong> sites, you can only <a href="https://help.github.com/en/github/working-with-github-pages/about-github-pages#publishing-sources-for-github-pages-sites">publish from the <code>master</code> branch</a>. This effectively prevents alternative GitHub Pages deployments such as the one described below for those types of sites.</p> <p>What are <strong>user</strong> or <strong>organization</strong> sites? Those are the sites that publish from repos named <code>&lt;user&gt;.github.io</code> (or <code>&lt;organization&gt;.github.io</code>) and have URLs that look like <code>https://&lt;user&gt;.github.io/</code>.</p> <p>All other repos are considered <strong>project</strong> sites. The deploy steps defined in this tutorial will work with those sites since they can <a href="https://help.github.com/en/github/working-with-github-pages/about-github-pages#publishing-sources-for-github-pages-sites">publish from the <code>gh-pages</code> branch</a>.</p> </blockquote> <h2 id="the-build" tabindex="-1">The build </h2> <p>This brief tutorial will cover the essentials of migrating from Jekyll to JavaScript. The following are required to get started:</p> <ul> <li><a href="https://github.com/">GitHub account</a></li> <li><a href="https://nodejs.org/">NodeJS</a> set up locally</li> </ul> <p>Of course, it would also make sense to already have an existing Jekyll repo. If not, then <a href="https://github.com/new/">create a new GitHub repo</a> (public or private) with a README initialized. Follow the follow-up instructions to clone and set it up locally.</p> <h3 id="set-up-the-static-site-generator" tabindex="-1">Set up the static site generator </h3> <p>It took awhile for someone to distill the essence of Jekyll into JavaScript. With <a href="https://11ty.dev/">Eleventy</a>, Zach Leatherman (<a href="https://twitter.com/zachleat">@zachleat</a>) has really distilled, bottled, and shipped it. Oh, and did I mention that it’s fast? I’ll skip the rest of the sales pitch here and let the experts do the talking.</p> <ul> <li><a href="https://daverupert.com/2019/08/what-i-like-about-eleventy/">Dave Rupert: What I Like About Eleventy</a></li> <li><a href="https://iandroid.eu/why-im-digging-eleventy/">iandroid: Why I’m Digging Eleventy</a></li> <li><a href="https://24ways.org/2018/turn-jekyll-up-to-eleventy/">Paul Lloyd: Turn Jekyll Up to Eleventy</a></li> </ul> <p>If you haven’t already guessed, I’m smitten with Eleventy. It’s that multi-purpose knife that fits just perfectly in your pocket and makes you smile every time you use it.</p> <p>It also makes the transition from Jekyll to JavaScript almost completely seamless. So let’s roll with it.</p> <h4>Install and configure</h4> <ol> <li> <p>Open a terminal and navigate to your repo.</p> </li> <li> <p>If you don’t already have a <code>package.json</code> file in the root directory, then take this moment to initialize npm.</p> <pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> init</code></pre> </li> <li> <p>Install Eleventy as a <code>devDependency</code>.</p> <pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> --save-dev @11ty/eleventy</code></pre> </li> <li> <p>To the site root, add an empty <code>.nojekyll</code> file to <a href="https://help.github.com/en/github/working-with-github-pages/about-github-pages#static-site-generators">disable the default Jekyll build</a>.</p> <pre class="language-shell"><code class="language-shell"><span class="token function">touch</span> .nojekyll</code></pre> </li> <li> <p>Keeping things simple, we’ll assume a <a href="https://jekyllrb.com/docs/structure/">default Jekyll file structure</a>.</p> <pre class="language-shell"><code class="language-shell"><span class="token punctuation">..</span><br />├── _layouts<br />│ ├── default.html<br />├── assets<br />│ ├── css<br />│ ├── images<br />│ └── js<br />├── index.html<br /><span class="token punctuation">..</span></code></pre> <p>To the site root, add a <code>.eleventy.js</code> config file with the following contents. For more details, see the <a href="https://www.11ty.dev/docs/config/">Eleventy configuration docs</a>.</p> <pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token comment">// Copy the "assets" directory to the compiled "_site" folder.</span><br /> eleventyConfig<span class="token punctuation">.</span><span class="token function">addPassthroughCopy</span><span class="token punctuation">(</span><span class="token string">'assets'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">return</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">dir</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">'./'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token string">'./_site'</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">layouts</span><span class="token operator">:</span> <span class="token string">'./_layouts'</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">templateFormats</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token string">'html'</span><span class="token punctuation">,</span><br /> <span class="token string">'liquid'</span><span class="token punctuation">,</span><br /> <span class="token string">'md'</span><span class="token punctuation">,</span><br /> <span class="token string">'njk'</span><span class="token punctuation">,</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">pathPrefix</span><span class="token operator">:</span> <span class="token string">'/&lt;repo_name>/'</span><span class="token punctuation">,</span> <span class="token comment">// omit this line if using custom domain</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>Looking at the <code>dir.output</code> above, notice that we are maintaining parity with Jekyll’s deployment, using the <code>./_site</code> directory for our compiled code.</p> <p>Also note that, unless you’re using a <a href="https://stedman.dev/2020/04/29/make-the-jump-from-jekyll-to-javascript/#the-custom-domain">custom domain</a>, you’ll need to add a <a href="https://www.11ty.dev/docs/config/#deploy-to-a-subdirectory-with-a-path-prefix">path prefix</a> (<code>/&lt;repo_name&gt;/</code>) to the config options. This is because <a href="https://help.github.com/en/github/working-with-github-pages/about-github-pages#types-of-github-pages-sites">project sites</a> are hosted on subdirectory URLs that look like <code>https://&lt;user&gt;.github.io/&lt;repo_name&gt;/</code>.</p> </li> <li> <p>To the <code>scripts</code> section of <code>package.json</code>, add the following. We’ll need the <code>build</code> script for our deployment later.</p> <pre class="language-json"><code class="language-json"> <span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"clean"</span><span class="token operator">:</span> <span class="token string">"rm -rf ./_site"</span><span class="token punctuation">,</span><br /> <span class="token property">"build"</span><span class="token operator">:</span> <span class="token string">"npm run clean &amp;&amp; eleventy"</span><span class="token punctuation">,</span><br /> <span class="token property">"start"</span><span class="token operator">:</span> <span class="token string">"eleventy --serve --watch"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre> <ul> <li><strong>clean</strong>: empties out the deployment directory</li> <li><strong>build</strong>: cleans the deploy directory and builds the site with Eleventy</li> <li><strong>start</strong>: runs Eleventy in developer mode with live browser refreshes</li> </ul> </li> </ol> <h2 id="the-deployment" tabindex="-1">The deployment </h2> <p>Now for the real magic sauce, <a href="https://docs.github.com/en/actions">GitHub Actions</a>-style.</p> <h3 id="set-up-a-workflow" tabindex="-1">Set up a workflow </h3> <ol> <li> <p>Open a browser to your GitHub repo and then select the <strong>Actions</strong> tab.</p> </li> <li> <p>Tap on the <strong>New workflow</strong> button.</p> </li> <li> <p>Tap on the <strong>Set up a workflow yourself</strong> button.</p> </li> <li> <p>In the <strong>{repo_name}/.github/workflows/<code>main.yml</code></strong> field, enter “<code>eleventy_build.yml</code>”.</p> </li> <li> <p>Into the <strong>Edit new file</strong> textarea, cut and paste the following:</p> <pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Eleventy Build<br /><br /><span class="token comment"># Controls when the action will run. Triggers the workflow on push or pull request</span><br /><span class="token comment"># events but only for the master branch</span><br /><span class="token key atrule">on</span><span class="token punctuation">:</span><br /> <span class="token key atrule">push</span><span class="token punctuation">:</span><br /> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> master <span class="token punctuation">]</span><br /><br /><span class="token comment"># A workflow run is made up of one or more jobs that can run sequentially or in parallel</span><br /><span class="token key atrule">jobs</span><span class="token punctuation">:</span><br /> <span class="token comment"># This workflow contains a single job called "build"</span><br /> <span class="token key atrule">build</span><span class="token punctuation">:</span><br /> <span class="token comment"># The type of runner that the job will run on</span><br /> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest<br /><br /> <span class="token comment"># Steps represent a sequence of tasks that will be executed as part of the job</span><br /> <span class="token key atrule">steps</span><span class="token punctuation">:</span><br /> <span class="token comment"># Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it</span><br /> <span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2<br /><br /> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Setup Node.js environment<br /> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>[email protected]<br /><br /> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install packages<br /> <span class="token key atrule">run</span><span class="token punctuation">:</span> npm ci<br /><br /> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Run npm build<br /> <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run build<br /><br /> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Deploy to gh<span class="token punctuation">-</span>pages<br /> <span class="token key atrule">uses</span><span class="token punctuation">:</span> peaceiris/actions<span class="token punctuation">-</span>gh<span class="token punctuation">-</span>pages@v3<br /> <span class="token key atrule">with</span><span class="token punctuation">:</span><br /> <span class="token key atrule">deploy_key</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.ACTIONS_DEPLOY_KEY <span class="token punctuation">}</span><span class="token punctuation">}</span><br /> <span class="token key atrule">publish_dir</span><span class="token punctuation">:</span> ./_site</code></pre> </li> <li> <p>Tap the <strong>Start commit</strong> button.</p> </li> <li> <p>Complete the <strong>Commit new file</strong> form and select <strong>Commit directly to the <code>master</code> branch</strong>.</p> </li> </ol> <p>The workflow script should be fairly self-descriptive. See that <code>${{ secrets.ACTIONS_DEPLOY_KEY }}</code> line near the end of the workflow? We need to add deploy keys to GitHub before attempting a deployment.</p> <h3 id="set-up-deployment-keys" tabindex="-1">Set up deployment keys </h3> <p>The following instructions were cribbed from a third-party <a href="https://github.com/marketplace/actions/github-pages-action#%EF%B8%8F-create-ssh-deploy-key">GitHub Actions for GitHub Pages</a> page. Please check that site for updates before proceeding as these instructions may have changed.</p> <ol> <li> <p>Generate a SSH key locally to use as your deploy key.</p> <pre class="language-shell"><code class="language-shell">ssh-keygen <span class="token parameter variable">-t</span> rsa <span class="token parameter variable">-b</span> <span class="token number">4096</span> <span class="token parameter variable">-C</span> <span class="token string">"<span class="token variable"><span class="token variable">$(</span><span class="token function">git</span> config user.email<span class="token variable">)</span></span>"</span> <span class="token parameter variable">-f</span> gh-pages <span class="token parameter variable">-N</span> <span class="token string">""</span><br /><span class="token comment"># You will get 2 files:</span><br /><span class="token comment"># gh-pages.pub (public key)</span><br /><span class="token comment"># gh-pages (private key)</span></code></pre> </li> <li> <p>Open a browser to your repo and select the <strong>Settings</strong> tab.</p> </li> <li> <p>Select <strong>Deploy keys</strong> from the left menu.</p> <ol> <li>Tap the <strong>Add deploy key</strong> button.</li> <li>In the <strong>Title</strong> field, enter “<code>Public key of ACTIONS_DEPLOY_KEY</code>”.</li> <li>In the <strong>Key</strong> field, cut and paste the contents of your <code>gh-pages.pub</code> SSH key file made in step 1 above.</li> <li>Check the <strong>Allow write access</strong> box.</li> <li>Tap the <strong>Add key</strong> button.</li> </ol> </li> <li> <p>Select <strong>Secrets</strong> from the left menu.</p> <ol> <li>Tap the <strong>Add a new secret</strong> link.</li> <li>In the <strong>Name</strong> field, enter “<code>ACTIONS_DEPLOY_KEY</code>”.</li> <li>In the <strong>Value</strong> field, cut and paste the contents of your <code>gh-pages</code> SSH key file from step 1 above.</li> <li>Tap the <strong>Add secret</strong> button.</li> </ol> <blockquote> <p>IMPORTANT: Delete the local <code>gh-pages</code> keys at this point. You certainly do not want to add them to your version control (for all to see).</p> </blockquote> </li> <li> <p>To initiate a deployment, commit/merge to the <code>master</code> branch. The first deploy will fail (because the <code>gh-pages</code> branch has not been created yet).</p> </li> <li> <p>Go back to the <strong>Settings</strong> tab.</p> <ol> <li>Scroll down to the <strong>GitHub Pages</strong> section.</li> <li>From the <strong>Source</strong> options, select <code>gh-pages branch</code>.</li> </ol> </li> <li> <p>Merge some more code into <code>master</code> to initiate another deploy.</p> </li> </ol> <p>Congrats. You’re done! From here on out, you should be able to trigger a fresh build and deploy by:</p> <ul> <li>working/saving locally, committing, and pushing to <code>origin master</code>.</li> <li>editing your code at <a href="http://github.com/">github.com</a> and saving to <code>master</code>.</li> </ul> <p>Your updates will deploy to live in mere seconds. All of this takes place within the ecosystem that you version your code in. There are no additional dependencies, no additional accounts, nada. How cool is that?</p> <h2 id="the-custom-domain" tabindex="-1">The custom domain </h2> <p>GitHub provides <a href="https://help.github.com/en/github/working-with-github-pages/about-github-pages#types-of-github-pages-sites">project sites</a> (e.g., <code>https://&lt;user&gt;.github.io/&lt;repo_name&gt;/</code>) that may suit you just fine. If, however, you want to use a custom domain (e.g., <code>https://example.com/</code>), there’s a bit more work involved.</p> <h3 id="set-up-a-custom-domain" tabindex="-1">Set up a custom domain </h3> <ol> <li> <p>If you haven’t already set up an apex domain (e.g., <code>example.com</code>) for this repo, then you will need to add it. Follow the <a href="https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site">GitHub instructions for managing a custom domain</a>.</p> </li> <li> <p>The <code>CNAME</code> file created when you entered your domain in the <strong>Custom domain</strong> field (above) needs to be included in our distribution directory.</p> <ol> <li> <p>Open the <code>gh-pages</code> branch and copy the <code>CNAME</code> file.</p> </li> <li> <p>Go back to the <code>master</code> branch and add that <code>CNAME</code> file to the source directory (in this case, the root directory).</p> </li> <li> <p>Open the <code>.eleventy.js</code> config file and add the CNAME file to the list of pass-through copies.</p> <pre class="language-js"><code class="language-js"><span class="token punctuation">.</span><span class="token punctuation">.</span><br /> eleventyConfig<span class="token punctuation">.</span><span class="token function">addPassthroughCopy</span><span class="token punctuation">(</span><span class="token string">'CNAME'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">.</span><span class="token punctuation">.</span></code></pre> </li> <li> <p>Save, commit, and push to origin.</p> </li> </ol> </li> </ol> <h2 id="conclusion" tabindex="-1">Conclusion </h2> <p>For 12 years, GitHub teased us with a web hosting product that almost did everything we needed. We were grateful and we made due. Along the way, we came up with hacks and workarounds, employing one or more outside services to complete the task. But nothing quite combined source code versioning, flexible site generation, and web hosting in one elegant package.</p> <p>With Eleventy and GitHub Actions, we now have it all.</p>