<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matt Swensen</title>
    <description>The latest articles on DEV Community by Matt Swensen (@mjswensen).</description>
    <link>https://dev.to/mjswensen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F152798%2F4c7edb3c-14ae-421c-b738-bd09da9ec07c.png</url>
      <title>DEV Community: Matt Swensen</title>
      <link>https://dev.to/mjswensen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mjswensen"/>
    <language>en</language>
    <item>
      <title>The single most important factor that differentiates front-end frameworks</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Sun, 11 Jun 2023 05:14:49 +0000</pubDate>
      <link>https://dev.to/mjswensen/the-single-most-important-factor-that-differentiates-front-end-frameworks-4pk6</link>
      <guid>https://dev.to/mjswensen/the-single-most-important-factor-that-differentiates-front-end-frameworks-4pk6</guid>
      <description>&lt;p&gt;There are tons of blog posts on the internet about how frameworks differ and which one to pick for your next web project. Usually they cover a few aspects of the framework like syntax, development setup, and community size.&lt;/p&gt;

&lt;p&gt;This isn’t one of those posts.&lt;/p&gt;

&lt;p&gt;Instead, we’ll go directly to the crux of the main problem front-end frameworks set out to solve: &lt;em&gt;change detection&lt;/em&gt;, meaning &lt;em&gt;detecting changes to application state so that the UI can be updated accordingly.&lt;/em&gt; Change detection is the fundamental feature of front-end frameworks, and the framework authors’ solution to this one problem determines everything else about it: developer experience, user experience, API surface area, community satisfaction and involvement, etc., etc.&lt;/p&gt;

&lt;p&gt;And it turns out that examining various frameworks from this perspective will give you all of the information you need to determine the best choice for you and for your users. So let’s dive deep into how each framework tackles change detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Major frameworks compared
&lt;/h2&gt;

&lt;p&gt;We’ll look at each of the major players and how they have tackled change detection, but the same critical eye can apply to any front-end JavaScript framework you may come across.&lt;/p&gt;

&lt;h3&gt;
  
  
  React
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’ll manage state so that I know when it changes.” —React&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;True to its de-facto tagline, change detection in React is “just JavaScript.” Developers simply update state by calling directly into the React runtime through its API; since React is notified to make the state change, it also knows that it needs to re-render the component.&lt;/p&gt;

&lt;p&gt;Over the years, the default style for writing components has changed (from &lt;a href="https://react.dev/reference/react/Component#defining-a-class-component"&gt;class components&lt;/a&gt; and &lt;a href="https://react.dev/reference/react/PureComponent#purecomponent"&gt;pure components&lt;/a&gt; to &lt;a href="https://react.dev/reference/react/Component#migrating-a-simple-component-from-a-class-to-a-function"&gt;function components&lt;/a&gt; to &lt;a href="https://react.dev/reference/react#state-hooks"&gt;hooks&lt;/a&gt;) but the core principle has remained the same. Here’s an example component that implements a button counter, written in the hooks style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;decrement&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;increment&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;increment later&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key piece here is the &lt;code&gt;setCount&lt;/code&gt; function returned to us by React’s &lt;code&gt;useState&lt;/code&gt; hook. When this function is called, React can use its internal virtual DOM diffing algorithm to determine which pieces of the page to re-render. Note that this means the React runtime has to be included in the application bundle downloaded by the user.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;React's change detection paradigm is straightforward: the application state is maintained inside the framework (with APIs exposed to the developer for updating it) so that React knows when to re-render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’ll make the developer do all the work.” —Angular&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you scaffold a new Angular application, it appears that change detection happens automagically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div&amp;gt;
      &amp;lt;button (click)="count = count - 1"&amp;gt;decrement&amp;lt;/button&amp;gt;
      &amp;lt;span&amp;gt;8&amp;lt;/span&amp;gt;
      &amp;lt;button (click)="count = count + 1"&amp;gt;increment&amp;lt;/button&amp;gt;
      &amp;lt;button (click)="incrementLater()"&amp;gt;increment later&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;incrementLater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s really happening, is that Angular uses &lt;a href="https://angular.io/guide/zone"&gt;&lt;code&gt;NgZone&lt;/code&gt;&lt;/a&gt; to observe user actions, and is checking &lt;em&gt;your entire component tree on every event.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For applications of any reasonable size, this causes performance issues, since checking the entire tree quickly becomes too costly. So Angular provides an escape hatch from this behavior by allowing the developer to choose a different change detection strategy: &lt;code&gt;OnPush&lt;/code&gt;. &lt;code&gt;OnPush&lt;/code&gt; means that the onus is on the developer to inform Angular when state changes so that Angular can re-render the component. Aside from the default naive strategy, &lt;a href="https://angular.io/api/core/ChangeDetectionStrategy"&gt;&lt;code&gt;OnPush&lt;/code&gt; is the only other change detection strategy Angular offers&lt;/a&gt;. With &lt;code&gt;OnPush&lt;/code&gt; enabled, we must manually tell Angular’s change detector to check the new state if it ever gets updated asynchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div&amp;gt;
      &amp;lt;button (click)="count = count - 1"&amp;gt;decrement&amp;lt;/button&amp;gt;
      &amp;lt;span&amp;gt;8&amp;lt;/span&amp;gt;
      &amp;lt;button (click)="count = count + 1"&amp;gt;increment&amp;lt;/button&amp;gt;
      &amp;lt;button (click)="incrementLater()"&amp;gt;increment later&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;cdr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectorRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;incrementLater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markForCheck&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For applications of any reasonable complexity, this approach quickly becomes untenable.&lt;/p&gt;

&lt;p&gt;Alternative solutions are introduced to wrangle this problem. The primary one that the Angular docs suggest is to use RxJS observables in conjunction with the &lt;a href="https://angular.io/api/common/AsyncPipe"&gt;&lt;code&gt;AsyncPipe&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;DECREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;INCREMENT_LATER&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div&amp;gt;
      &amp;lt;button (click)="update.next(Action.DECREMENT)"&amp;gt;decrement&amp;lt;/button&amp;gt;
      &amp;lt;span&amp;gt;8&amp;lt;/span&amp;gt;
      &amp;lt;button (click)="update.next(Action.INCREMENT)"&amp;gt;increment&amp;lt;/button&amp;gt;
      &amp;lt;button (click)="update.next(Action.INCREMENT_LATER)"&amp;gt;increment later&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;changeDetection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChangeDetectionStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnPush&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;switchScan&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INCREMENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DECREMENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INCREMENT_LATER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;startWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, &lt;code&gt;AsyncPipe&lt;/code&gt; takes care of subscribing to the observable, informing the change detector when the observable emits a new value, and unsubscribing when the component is destroyed. Observables are a powerful way to model state changes over time, but they come with some serious drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They are difficult to debug.&lt;/li&gt;
&lt;li&gt;They have a very steep learning curve.&lt;/li&gt;
&lt;li&gt;They are great for modeling streams of values (think: mouse movements), but they are overkill for the more common use cases (simple state changes like the on/off state of a checkbox).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To overcome the shortcomings of the default change detection paradigm, the Angular team is working on a new approach called &lt;a href="https://angular.io/guide/signals"&gt;Signals&lt;/a&gt;. Conceptually, signals are similar to Svelte stores (which we’ll get to later), and fundamentally, they solve the change detection problem the same way as React; the framework is taking control over the application’s state so that changes can be easily monitored and re-renders can be as efficient as possible.&lt;/p&gt;

&lt;p&gt;From the Angular docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a large paradigm shift, making Angular applications more similar to the other frameworks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;Angular's change detection is a disaster. The developer gets two suboptimal choices: (1) the slow and naive default implementation, or the complexity of managing change detection manually. Signals will make it much better, though nearly a decade too late.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’ll track changes to state and react accordingly.” —Vue&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vue’s approach to change detection is subtly different than both React and Angular. Rather than calling a framework function to change state (React) or changing state and then informing the framework that it has been changed (Angular), you work with state objects that have been specially instrumented by the framework to intercept and detect changes.&lt;/p&gt;

&lt;p&gt;Confusingly, Vue has two different APIs that wrap the same underlying change detection engine differently. Under the “Options API,” you define an object that contains your state, and Vue assigns a proxied version of that object as a member of &lt;code&gt;this&lt;/code&gt; for use in the component’s functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"decrement"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;decrement&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;8&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;increment&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"incrementLater"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;increment later&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;incrementLater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, the “Composition API” is somewhat similar to React’s hooks: a framework function is called to retrieve a state object that Vue can monitor for changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;incrementLater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"decrement"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;decrement&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;8&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;increment&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"incrementLater"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;increment later&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://vuejs.org/guide/essentials/reactivity-fundamentals.html#why-refs"&gt;Conceptually, the object returned from &lt;code&gt;ref()&lt;/code&gt; has a getter and a setter for &lt;code&gt;value&lt;/code&gt;&lt;/a&gt;, which allows Vue to track changes to it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;Vue utilizes JavaScript language features to allow developers to work with stateful variables without thinking about change detection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Svelte
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’ll figure it out for you at compile time.” —Svelte&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the surface, Svelte’s version of our counter component looks pretty similar to the other frameworks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;incrementLater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"{decrement}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;decrement&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;{count}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"{increment}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;increment&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"{incrementLater}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;increment later&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Svelte’s approach to change detection is completely novel in comparison. At compile time, Svelte analyzes an AST (Abstract Syntax Tree) of the component’s code and &lt;em&gt;injects some code into the compiled output&lt;/em&gt; that surgically updates the DOM when necessary. For example, here is what the compiled &lt;code&gt;decrement()&lt;/code&gt; function looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;$$invalidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;$$invalidate&lt;/code&gt; is a call to Svelte’s internals to instruct the compiled component to update the DOM.&lt;/p&gt;

&lt;p&gt;This compile-time approach means that Svelte applications don’t need to bundle a large runtime along with the application itself.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;Svelte strikes a rare win-win balance: developers don't have to think about change detection at all, and can interact with stateful variables intuitively; yet the end user's experience is improved through better performance because a bare-minimum application (with change detection baked in) is shipped to the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what?
&lt;/h2&gt;

&lt;p&gt;The nuances of how various frameworks choose to tame this beast is not limited to how things work at the component level; it ripples out to &lt;em&gt;everything else&lt;/em&gt; about the framework. To name just a few examples: the concepts used to &lt;a href="https://react.dev/learn/reusing-logic-with-custom-hooks"&gt;create custom React hooks&lt;/a&gt; composed of the basic hooks provided by React out of the box are not relevant to generalizing component behavior in Vue; the challenge of working with observables for state management in Angular has led folks to &lt;a href="https://github.com/futhark/ngx-observable-input"&gt;try and find ways to convert component input props to observables&lt;/a&gt;; the framework’s API, dictated by its change detection management paradigm, affects how well it integrates with productivity and quality tools like typechecking, testing, and linting. And so on, and so forth.&lt;/p&gt;

&lt;p&gt;And those are just examples from the developer’s point of view. Each approach has implications on the performance of the application for the end user. React, Vue, and Angular each ship a runtime to the user’s browser that needs to be parsed and executed. Svelte’s choice to be a compile-time framework obviates this need in most cases, so the user gets a faster loading experience. Each framework has subtleties that make it more susceptible to particular classes of bugs (often around state management or change detection) that the end user will experience.&lt;/p&gt;

&lt;p&gt;Find a change detection paradigm that fits the needs of your application, and everything else will fall into place. Pick one that doesn’t work, and you’ll be fighting against it for the life of the project.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to set up a Wireguard VPN for personal use</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Sun, 19 Mar 2023 00:04:28 +0000</pubDate>
      <link>https://dev.to/mjswensen/how-to-set-up-a-wireguard-vpn-for-personal-use-38fp</link>
      <guid>https://dev.to/mjswensen/how-to-set-up-a-wireguard-vpn-for-personal-use-38fp</guid>
      <description>&lt;p&gt;VPNs are a great way to level up your privacy online. Here’s how to set one up from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: spin up and connect to your cloud server
&lt;/h2&gt;

&lt;p&gt;Using your cloud provider of choice, provision a new server instance. Since the VPN will be for personal use only, inexpensive instances will work fine. Select any operating system that Wireguard supports; we’ll assume Ubuntu for this tutorial.&lt;/p&gt;

&lt;p&gt;Connect to your server via SSH as the &lt;code&gt;root&lt;/code&gt; user.&lt;/p&gt;

&lt;p&gt;Root user&lt;/p&gt;

&lt;p&gt;Heads up, all commands outlined in this tutorial assume the root user, so you won't see any commands prefixed with &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: install Wireguard
&lt;/h2&gt;

&lt;p&gt;If you picked Ubuntu for your server’s OS, you can install Wireguard with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt install wireguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Working directory&lt;/p&gt;

&lt;p&gt;For the remaining steps, change your working directory to &lt;code&gt;/etc/wireguard&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: generate a key pair for your VPN server
&lt;/h2&gt;

&lt;p&gt;First, generate a private key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wg genkey &amp;gt; private.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, use the private key to derive a public key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wg pubkey &amp;lt; private.key &amp;gt; public.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the keys your VPN server will use when encrypting traffic with your client VPN peers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: create your server configuration file
&lt;/h2&gt;

&lt;p&gt;With Wireguard installed and keys generated, we’re ready to create our VPN configuration.&lt;/p&gt;

&lt;p&gt;First, find the name of the public network interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ip route list default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output might look something like this: &lt;code&gt;default via ... dev eth0 proto static&lt;/code&gt;—the &lt;code&gt;eth0&lt;/code&gt; part is what we’re after.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;wg0.conf&lt;/code&gt; with the following contents, being sure to include the private key where required, and replacing &lt;code&gt;eth0&lt;/code&gt; with the name of the network interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Interface]
SaveConfig = true
PostUp = ufw route allow in on wg0 out on eth0
PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PreDown = ufw route delete allow in on wg0 out on eth0
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 51820
PrivateKey = &amp;lt;the contents of private.key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: configure IP forwarding
&lt;/h2&gt;

&lt;p&gt;Add the following line at the bottom of &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net.ipv4.ip_forward=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;sysctl -p&lt;/code&gt; to load the new configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: allow UDP traffic
&lt;/h2&gt;

&lt;p&gt;Update the operating system firewall to allow traffic on the port specified in the Wireguard configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ufw allow 51820/udp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart UFW to pick up the new configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ufw disable
ufw enable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7: configure the Wireguard server to start on boot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemctl enable wg-quick@wg0.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8: generate keys for clients
&lt;/h2&gt;

&lt;p&gt;Now we’ll generate keys for the devices that will be connecting to the server. Follow this process for as many clients as you need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Output the private key with &lt;code&gt;wg genkey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Output the corresponding public key with &lt;code&gt;echo -n '&amp;lt;the private key&amp;gt;' | wg pubkey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mark the public key as allowed with &lt;code&gt;wg set wg0 peer &amp;lt;the public key&amp;gt; allowed-ips 10.8.0.2&lt;/code&gt;; for each client increment the last digit of the IP address&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify that the public keys have been added as peers by running the &lt;code&gt;wg&lt;/code&gt; command with no arguments. If you’ve configured three clients, the output should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface: wg0
  public key: &amp;lt;the server's public key&amp;gt;
  private key: (hidden)
  listening port: 51820

peer: &amp;lt;first client's public key&amp;gt;
  allowed ips: 10.8.0.2/32

peer: &amp;lt;second client's public key&amp;gt;
  allowed ips: 10.8.0.3/32

peer: &amp;lt;third client's public key&amp;gt;
  allowed ips: 10.8.0.4/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 9: configure clients
&lt;/h2&gt;

&lt;p&gt;Finally, configure your client devices to connect to your Wireguard VPN server. This process will be a little different depending on whether you are using the iOS app, Android app, or another Linux installation of Wireguard. Generally, though, your client configuration should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Interface]
PrivateKey = &amp;lt;the client private key&amp;gt;
Address = &amp;lt;the client IP address&amp;gt;/24
DNS = &amp;lt;the DNS resolvers you wish to use (optional)&amp;gt;

[Peer]
PublicKey = &amp;lt;the server public key&amp;gt;
Endpoint = &amp;lt;the server IP address&amp;gt;:51820
AllowedIPs = 0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;0.0.0.0/0&lt;/code&gt; configuration value indicates that all internet traffic should be tunnelled through the VPN connection.&lt;/p&gt;




&lt;p&gt;Happy tunneling!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to develop over HTTPS on localhost</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Fri, 11 Mar 2022 15:16:41 +0000</pubDate>
      <link>https://dev.to/mjswensen/how-to-develop-over-https-on-localhost-1cb5</link>
      <guid>https://dev.to/mjswensen/how-to-develop-over-https-on-localhost-1cb5</guid>
      <description>&lt;p&gt;There are a number of reasons you may wish to use HTTPS as part of your web application development workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app will likely be deployed to users via HTTPS; the closer your development environment is to production, the less likely you are to run into bugs.&lt;/li&gt;
&lt;li&gt;Browsers behave differently in secure contexts, and you want to avoid production-only surprises.&lt;/li&gt;
&lt;li&gt;You might be integrating with third-party services or APIs that require the use of HTTPS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep reading to find out how to set up SSL for local development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Generate a self-signed SSL certificate
&lt;/h2&gt;

&lt;p&gt;Unless you are developing on a &lt;a href="https://youtu.be/zivPD-LL57k"&gt;remote development environment&lt;/a&gt; with a static IP address, you won’t have a unique domain name that points to your development environment (and even if you are, you may not want or need one). Without a unique domain name, our only option for and SSL certificate is a self-signed one for local use only. Use this command to generate one:&lt;/p&gt;

&lt;p&gt;tl;dr: Use the command below to generate your self-signed certificate and private key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -subj '/CN=localhost' -extensions EXT -config &amp;lt;(printf "[req]\ndistinguished_name = dn\n[dn]\nCN=localhost\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s pick that command apart. Feel free to skip to step 2 if you don’t care about the details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openssl req&lt;/code&gt; - We’re using the &lt;code&gt;openssl&lt;/code&gt; program to generate our certificate. &lt;code&gt;openssl&lt;/code&gt; has many sub-commands; the one we are using is &lt;a href="https://www.openssl.org/docs/man1.1.1/man1/openssl-req.html"&gt;&lt;code&gt;req&lt;/code&gt;, which is for generating certificates and certificate requests&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-x509&lt;/code&gt; - Output a self-signed certificate (rather than a certificate signing request that you might send off to a certificate authority—often called a “CA” for short—to be verified and signed for production use).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-out localhost.crt&lt;/code&gt; - Write the certificate to a file called &lt;code&gt;localhost.crt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-keyout localhost.key&lt;/code&gt; - Write the private key to a file called &lt;code&gt;localhost.key&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-newkey rsa:2048&lt;/code&gt; - Generate and use a new private key (as opposed to an existing key, which you’d specify with the &lt;code&gt;-key&lt;/code&gt; option) of 2048 bits using the RSA algorithm.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-nodes&lt;/code&gt; - Do not encrypt the private key.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-subj '/CN=localhost'&lt;/code&gt; - Sets the subject of the certificate request. For production certs, this would include information about the company or business seeking the certificate (such as location, department, email address, etc.), but since this is a development-only certificate, the only necessary information is the “common name” (“CN” for short), &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-extensions EXT&lt;/code&gt; - Specifies that the extensions section of our config is delineated with a header titled &lt;code&gt;EXT&lt;/code&gt;. That config is defined in the next part of the command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-config &amp;lt;(printf "...")&lt;/code&gt; - The &lt;code&gt;-config&lt;/code&gt; option takes a file name containing configuration options, but rather than use a separate file we use input redirection (the &lt;code&gt;&amp;lt;&lt;/code&gt; character) and a subshell (the command embedded between parentheses, which returns the string config) to pass the config contents directly, for convenience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contents of the configuration are shown more readably below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[req]
distinguished_name = dn
[dn]
CN=localhost
[EXT]
subjectAltName=DNS:localhost
keyUsage=digitalSignature
extendedKeyUsage=serverAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[req]&lt;/code&gt; - Options for the &lt;code&gt;req&lt;/code&gt; subcommand are below this heading.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;distinguished_name = dn&lt;/code&gt; - Indicates that the distinguished name fields will be found in a section of the config titled &lt;code&gt;dn&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[dn]&lt;/code&gt; - The heading for the fields as indicated above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CN=localhost&lt;/code&gt; - The common name (“CN”) for the certificate; in this case: &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[EXT]&lt;/code&gt; - The heading for the extensions, as indicated by the &lt;code&gt;-extensions&lt;/code&gt; parameter above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;subjectAltName=DNS:localhost&lt;/code&gt; - Indicates that the certificate should match the &lt;code&gt;localhost&lt;/code&gt; domain name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keyUsage=digitalSignature&lt;/code&gt; - Indicates that the key may be used for signing the certificate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extendedKeyUsage=serverAuth&lt;/code&gt; - Indicate that in addition to signing the certificate, the private key will also be used to identify the server for SSL/TLS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Install and trust the certificate
&lt;/h2&gt;

&lt;p&gt;Now your local operating system needs to know that the certificate is trustworthy. (Otherwise you’ll see a warning when you access &lt;code&gt;https://localhost&lt;/code&gt; that the certificate is not trusted. This warning can be bypassed but trusting the certificate at the OS level gives a smoother development experience.)&lt;/p&gt;

&lt;p&gt;For macOS, this can be achieved by following these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Keychain Access app.&lt;/li&gt;
&lt;li&gt;Drag and drop your &lt;code&gt;localhost.crt&lt;/code&gt; file onto the app.&lt;/li&gt;
&lt;li&gt;Right click on the new certificate entry and choose “Get Info”.&lt;/li&gt;
&lt;li&gt;Expand the “Trust” section of the Get Info dialog.&lt;/li&gt;
&lt;li&gt;For “Secure Sockets Layer (SSL)”, choose “Always Trust”.&lt;/li&gt;
&lt;li&gt;Close the dialog and input your password (or TouchID) to save the changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There should be a similar process for trusting the certificate in Windows and Linux.&lt;/p&gt;

&lt;p&gt;If you’re working on a team, write some documentation for your teammates on how to do this (or point them to this article) so that new engineers can get up and running quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Configure your development server to use the certificate and private key
&lt;/h2&gt;

&lt;p&gt;Lastly, your development server applications need to be updated to use the certificate and private key. This will be different for each language and framework. Here are some examples:&lt;/p&gt;

&lt;p&gt;For Netlify, the &lt;code&gt;netlify dev&lt;/code&gt; server can be configured to use your SSL certificate in development by adding the following to your &lt;code&gt;netlify.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[dev.https]
  certFile = "localhost.crt"
  keyFile = "localhost.key"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Node.js servers, &lt;a href="https://nodejs.org/dist/latest-v16.x/docs/api/https.html#httpscreateserveroptions-requestlistener"&gt;pass the &lt;code&gt;key&lt;/code&gt; and &lt;code&gt;cert&lt;/code&gt; options to &lt;code&gt;https.createServer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once configured, you should be able to access your development server via HTTPS on &lt;code&gt;localhost&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/certificates-for-localhost/"&gt;Certificates for localhost article by Let’s Encrypt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/man1.1.1/man1/openssl-req.html"&gt;&lt;code&gt;openssl req&lt;/code&gt; manual page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>How to set up a simple web server development container for VS Code</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Wed, 26 Jan 2022 16:18:46 +0000</pubDate>
      <link>https://dev.to/mjswensen/how-to-set-up-a-simple-web-server-development-container-for-vs-code-5d45</link>
      <guid>https://dev.to/mjswensen/how-to-set-up-a-simple-web-server-development-container-for-vs-code-5d45</guid>
      <description>&lt;p&gt;Sometimes it’s nice to have a lightweight development container that automatically serves the contents of a directory, for rapid prototyping or building static web pages—without having to setup a tool chain on your local environment or install web server software (and its dependencies) on your local machine.&lt;/p&gt;

&lt;p&gt;If you’re using VS Code’s extension for developing inside containers, below is a recipe for a comfortable development setup containing these three main attributes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lightweight; small image footprint and minimal container boot up time&lt;/li&gt;
&lt;li&gt;Automatic; once the container starts, the files are being served on a local port without the need to run another command&lt;/li&gt;
&lt;li&gt;Complete; has all the dependencies required by VS Code for the full development lifecycle&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To achieve this, we’ll use the &lt;a href="https://alpinelinux.org/"&gt;Alpine Linux&lt;/a&gt; operating system with &lt;a href="https://nginx.org/en/"&gt;Nginx&lt;/a&gt; as our development web server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: define development container in a Dockerfile
&lt;/h2&gt;

&lt;p&gt;We’ll include the &lt;a href="https://code.visualstudio.com/docs/remote/linux#_remote-host-container-wsl-linux-prerequisites"&gt;dependencies required by VS Code’s remote containers extension&lt;/a&gt; as well as the specific tools we need for development.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.devcontainer/Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# We'll base our dev container image on Alpine Linux.
FROM alpine:3.15

# Then we'll add the dependencies we need:
# - musl, libgcc, and libstdc++ are required by VS Code's server
# - git and gnupg are useful for remote development so that we
# can commit and sign commits from within the container
# - and of course nginx itself
RUN apk add \
  musl \
  libgcc \
  libstdc++ \
  git \
  gnupg \
  nginx

# Finally, we'll remove nginx's default content and replace it
# with a symlink to our source code.

RUN rm -rf /usr/share/nginx/html
RUN ln -s /workspaces/my-app /usr/share/nginx/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heads up&lt;/p&gt;

&lt;p&gt;You'll want to replace &lt;code&gt;/workspaces/my-app&lt;/code&gt; with whatever directory VS Code mounts your source code to inside the container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: configure VS Code to utilize our image
&lt;/h2&gt;

&lt;p&gt;With our image definition done, we’ll add some configuration to instruct VS Code to run it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "my-app",
  // Tell VS Code to build our dev container from our Dockerfile.
  "build": {
    "dockerfile": "./Dockerfile"
  },
  // Automatically expose port 8080 (mapped to container's port 80, nginx's default) for development
  "appPort": "8080:80",
  // Make sure our nginx container's default command runs, which starts up the web server in the background.
  "overrideCommand": false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: profit
&lt;/h2&gt;

&lt;p&gt;With those files in place, VS Code has everything it needs to spin up the development container with the source code mounted inside. Once it’s running, you can load up your project’s files in the browser (or even try VS Code’s built-in development browser) on port &lt;code&gt;8080&lt;/code&gt; for easy side-by-side development:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Uf24-a1U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/simple-web-server-development-container-vs-code.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uf24-a1U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/simple-web-server-development-container-vs-code.png" alt="screenshot of code and browser side-by-side" width="880" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy prototyping!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Make an .ico File Using the macOS Terminal</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Thu, 11 Feb 2021 13:03:36 +0000</pubDate>
      <link>https://dev.to/mjswensen/how-to-make-an-ico-file-using-the-macos-terminal-5aan</link>
      <guid>https://dev.to/mjswensen/how-to-make-an-ico-file-using-the-macos-terminal-5aan</guid>
      <description>&lt;p&gt;Suppose you have an icon or logo at multiple resolutions and would like to combine them into a single &lt;code&gt;favicon.ico&lt;/code&gt; file for your website. These are the commands you need to get up and running from the comfort of your own terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: install ImageMagick
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;a href="https://imagemagick.org/script/download.php"&gt;ImageMagick downloads page&lt;/a&gt; for instructions on how to get it running on your machine. Personally, I like to use containerization to keep my Mac free from extraneous software that I rarely use, so I’ll install ImageMagick in a Docker container instead (make sure you have Docker installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it --rm -v /absolute/path/to/icons/directory:/workdir -w /workdir ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will drop you into a &lt;code&gt;bash&lt;/code&gt; session on the container. From there, it’s easy to get ImageMagick installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt update
apt upgrade
apt install -y imagemagick
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tip!&lt;/p&gt;

&lt;p&gt;ImageMagick has a dependency on &lt;code&gt;tzdata&lt;/code&gt;, so you will be prompted to configure your time zone upon installation. Since this is an ephemeral installation, you can just type &lt;code&gt;1&lt;/code&gt; for each prompt to get through the installation process quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: create the file
&lt;/h2&gt;

&lt;p&gt;ImageMagick gives us a handy &lt;code&gt;convert&lt;/code&gt; command that makes the process super easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;convert input-file-16x16.png input-file-24x24.png input-file-32x32.png input-file-64x64.png favicon.ico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the desired &lt;code&gt;favicon.ico&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: verify the .ico file
&lt;/h2&gt;

&lt;p&gt;ImageMagick also provides a nice utility called &lt;code&gt;identify&lt;/code&gt; for inspecting our file to ensure it is formatted as we expect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;identify favicon.ico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;favicon.ico[0] ICO 16x16 16x16+0+0 8-bit sRGB 0.000u 0:00.000
favicon.ico[1] ICO 24x24 24x24+0+0 8-bit sRGB 0.000u 0:00.000
favicon.ico[2] ICO 32x32 32x32+0+0 8-bit sRGB 0.000u 0:00.000
favicon.ico[3] ICO 64x64 64x64+0+0 8-bit sRGB 24838B 0.000u 0:00.000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>You Might Not Need Sass: Modern CSS Techniques</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Tue, 15 Sep 2020 12:46:12 +0000</pubDate>
      <link>https://dev.to/mjswensen/you-might-not-need-sass-modern-css-techniques-15g</link>
      <guid>https://dev.to/mjswensen/you-might-not-need-sass-modern-css-techniques-15g</guid>
      <description>&lt;p&gt;&lt;a href="https://sass-lang.com/"&gt;Sass&lt;/a&gt; and other CSS preprocessors have some great features and have been widely used for years. CSS is rapidly evolving, though, and some of the features that were previously only possible through preprocessors are now achievable in plain CSS. While updating the styles for &lt;a href="https://mjswensen.com"&gt;mjswensen.com&lt;/a&gt;, I found plain CSS to be powerful enough on its own and completely migrated away from Sass. Depending on the size and needs of your project (and target browser support), you may be able to simplify your CSS tool chain as well by using these techniques.&lt;/p&gt;

&lt;p&gt;The Sass features we will be re-creating in vanilla CSS in this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameterized Mixins&lt;/li&gt;
&lt;li&gt;Color Manipulation&lt;/li&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Parameterized Mixins
&lt;/h2&gt;

&lt;p&gt;In Sass, you can define a collection of reusable styles, and then include the styles in multiple places throughout your stylesheet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@mixin&lt;/span&gt; &lt;span class="nf"&gt;alert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nd"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nd"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submitted successfully.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can achieve this same functionality in vanilla CSS using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*"&gt;custom properties&lt;/a&gt; and some changes to our markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.alert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert success"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submitted successfully.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tradeoffs
&lt;/h3&gt;

&lt;p&gt;Though we can achieve something similar to Sass’s &lt;code&gt;@mixin&lt;/code&gt;/&lt;code&gt;@include&lt;/code&gt; with vanilla CSS, there are some tradeoffs to this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is less clarity around how to use the &lt;code&gt;alert&lt;/code&gt; class since it’s &lt;code&gt;--color&lt;/code&gt; “parameter” is implicit; you have to reference the HTML to see how it is being used and what the value of &lt;code&gt;--color&lt;/code&gt; might be, whereas with Sass the parameters are explicit.&lt;/li&gt;
&lt;li&gt;Sass can catch incorrect usages of mixins at compile time and surface errors to the developer, whereas the CSS approach will silently fail if used incorrectly.&lt;/li&gt;
&lt;li&gt;Since the CSS approach involves both the stylesheet and the markup, there is more room for mistakes in refactoring a “mixin,” especially in large projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Color Manipulation
&lt;/h2&gt;

&lt;p&gt;Sass has long provided a powerful library for manipulating colors. Similar functionality has been proposed for CSS in the &lt;a href="https://drafts.csswg.org/css-color/#modifying-colors"&gt;CSS Color Module Level 4 specification&lt;/a&gt; but is not available for widespread use at the time of writing. However, using the widely available CSS &lt;code&gt;calc()&lt;/code&gt; function, we can achieve some simple color manipulations without any tooling on top of our CSS.&lt;/p&gt;

&lt;p&gt;In Sass, we can use the &lt;code&gt;rgba()&lt;/code&gt; and &lt;code&gt;desaturate()&lt;/code&gt; functions to get semi-transparent and desaturated versions of a given color, respectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nv"&gt;$main-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;58%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$semi-transparent-main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$main-color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$desaturated-main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;desaturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$main-color&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can achieve the same effect in CSS by splitting the color components into their own custom properties and operating on them individually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--main-color-h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--main-color-s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--main-color-l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;58%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--semi-transparent-main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color-h&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color-s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color-l&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="m"&gt;0.1&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--desaturated-main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color-h&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color-s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;20%&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--main-color-l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tradeoffs
&lt;/h3&gt;

&lt;p&gt;The Sass syntax for color manipulation is more compact, easier to use, and easier to read. There are also limitations to the CSS-only approach, at least until the &lt;code&gt;color()&lt;/code&gt; function becomes a W3C recommendation and gains browser adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables
&lt;/h2&gt;

&lt;p&gt;One of the most then-groundbreaking features of CSS preprocessors was the ability to define and reuse values via variables. As seen in the examples above, we can now do the same thing in vanilla CSS using custom properties. But there are some nuances in the difference between Sass variables and custom properties that are worth mentioning.&lt;/p&gt;

&lt;p&gt;Since Sass is a compiled language (compiles down to CSS), variable values are evaluated at compile time rather than at runtime. This comes with a couple of tradeoffs to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sass variables’ values can’t change in response to browser context changes (like media queries), while custom properties can.&lt;/li&gt;
&lt;li&gt;Sass can warn you if you try to use a variable that hasn’t been initiated (if the name is misspelled, for example), while CSS will fall silently when using an undefined variable.&lt;/li&gt;
&lt;li&gt;Both Sass variables and custom properties are “global,” but “global” has different meanings. For Sass, “global” means in scope for the current compilation target and any dependency stylesheets (and their dependencies). In CSS, “global” means really global: the values are visible to all stylesheets, markup, and scripts loaded on the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Modern vanilla CSS can cover some of the preprocessors’ flagship features, but at a cost of readability and maintainability. The overhead of a CSS build process may be worth it for larger projects or teams. For smaller or solo projects, though, CSS is powerful enough to stand on its own.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building Stripe.com's Tabbed Preview Widget From Scratch in 30 Minutes</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Thu, 09 Apr 2020 12:39:00 +0000</pubDate>
      <link>https://dev.to/mjswensen/building-stripe-com-s-tabbed-preview-widget-from-scratch-in-30-minutes-53fi</link>
      <guid>https://dev.to/mjswensen/building-stripe-com-s-tabbed-preview-widget-from-scratch-in-30-minutes-53fi</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/NFOidUvke0k"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In this video I try to build a fully functional tabbed preview widget in HTML and CSS from scratch in 30 minutes—without looking at the original code. After the time’s up, I peek under the hood to see the approach of the original author and compare and contrast it to my approach.&lt;/p&gt;

&lt;h1&gt;
  
  
  The build process
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Setting up a development environment
&lt;/h2&gt;

&lt;p&gt;For this short project I used a very simple setup: an &lt;code&gt;index.html&lt;/code&gt; file served by &lt;a href="https://npmjs.com/package/browser-sync"&gt;&lt;code&gt;browser-sync&lt;/code&gt;&lt;/a&gt; for automatic reloads on save. It can be run without previous download or install via &lt;a href="https://www.npmjs.com/package/npx"&gt;&lt;code&gt;npx&lt;/code&gt;&lt;/a&gt;, which is included by default in node/npm installations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx browser-sync &lt;span class="nt"&gt;--server&lt;/span&gt; &lt;span class="nt"&gt;--files&lt;/span&gt; index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring the widget’s container
&lt;/h2&gt;

&lt;p&gt;Since this widget doesn’t appear to respond to the window size, I used the macOS screenshot tool (&lt;code&gt;command&lt;/code&gt;-&lt;code&gt;shift&lt;/code&gt;-&lt;code&gt;4&lt;/code&gt;) to measure the dimensions and simply hard-code them into the CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing the active tab state
&lt;/h2&gt;

&lt;p&gt;The original Stripe implementation uses JavaScript to maintain the state of the currently active tab, which is a perfectly reasonable approach. I thought it might be fun to see if we could do it without JavaScript. I landed on using radio-type HTML inputs and some CSS selector tricks to achieve the same effect. In a professional setting, I would have likely used JavaScript—one could argue that this is an inappropriate use of radio inputs since this is an informational widget and not part of a form with user-provided data.&lt;/p&gt;

&lt;p&gt;The book I referenced in the video is called &lt;a href="https://resilientwebdesign.com/"&gt;&lt;em&gt;Resilient Web Design&lt;/em&gt; by Jeremy Keith&lt;/a&gt; and is freely available to read online.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sliding the content left and right based on the active tab
&lt;/h2&gt;

&lt;p&gt;I wrapped the code snippets in a container and positioned it absolutely, altering the &lt;code&gt;left&lt;/code&gt; property based on the active tab. We discuss a better approach to this later on when we inspect Stripe’s solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving the widget a 3D appearance
&lt;/h2&gt;

&lt;p&gt;To give the widget a 3D appearance, I rotated the widget around the X and Y axes, but it didn’t quite have the right effect. I should have used the &lt;code&gt;rotate3d()&lt;/code&gt; function instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a shine effect
&lt;/h2&gt;

&lt;p&gt;For the shine effect, I added an &lt;code&gt;::after&lt;/code&gt; pseudo-element, positioned absolutely to stretch the width and height of the container, and added a background gradient. To keep the text beneath it selectable (and tabs clickable), &lt;code&gt;pointer-events: none&lt;/code&gt; was required so that mouse events would fall through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  My final code
&lt;/h2&gt;

&lt;p&gt;Here is what I ended up with at the end of the session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Code Preview Widget&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="py"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;490px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;--bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#31335B&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;--border-active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#596481&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;380px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-10deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.container&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;bottom&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hsla&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="py"&gt;grid-column-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-column-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.slide-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;overflow-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt; &lt;span class="m"&gt;400ms&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#js&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nc"&gt;.slide-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#rb&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nc"&gt;.slide-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#py&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nc"&gt;.slide-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#go&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nc"&gt;.slide-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#other&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nc"&gt;.slide-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--width&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"radio"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#js&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"js"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="nf"&gt;#rb&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rb"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="nf"&gt;#py&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"py"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="nf"&gt;#go&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"go"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="nf"&gt;#other&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"other"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;border-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2D2F4A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-top-left-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;border-top-right-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.25em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"js"&lt;/span&gt; &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"py"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"go"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"other"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Node.js
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Ruby
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"py"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Python
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"go"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Go
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"other"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"slide-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;// Set your secret key
  const stripe = require('stripe')('sk_test_BQokikJOvBiI2HlWgH4olfQ2');

  // Get the payment token ID submitted by the form:
  const token = request.body.stripeToken;

  (async () =&amp;gt; {
    const charge = await stripe.charges.create({
      amount: 999,
      currency: 'usd',
      description: 'Example charge',
      source: token,
    });
  })();&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;# Set your secret key
  Stripe.api_key = 'sk_test_BQokikJOvBiI2HlWgH4olfQ2'

  # Get the payment token ID submitted by the form:
  token = params[:stripeToken]

  charge = Stripe::Charge.create({
    amount: 999,
    currency: 'usd',
    description: 'Example charge',
    source: token,
  })&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt; Set your secret key
  stripe.api_key = 'sk_test_BQokikJOvBiI2HlWgH4olfQ2'

  # Get the payment token ID submitted by the form:
  token = request.form['stripeToken']

  charge = stripe.Charge.create(
    amount=999,
    currency='usd',
    description='Example charge',
    source=token,
  )&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;// Set your secret key
  stripe.Key = 'sk_test_BQokikJOvBiI2HlWgH4olfQ2'

  // Get the payment token ID submitted by the form:
  token := r.FormValue('stripeToken')

  params := &lt;span class="err"&gt;&amp;amp;&lt;/span&gt;stripe.ChargeParams{
    Amount: 999,
    Currency: 'usd',
    Description: 'Example charge',
  }
  params.SetSource(token)
  ch, _ := charge.New(params)&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;TODO&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Inspecting the original Stripe code
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Differences with my approach
&lt;/h2&gt;

&lt;p&gt;Aside from the fact that the Stripe code was much more polished (with additional borders, typography, syntax highlighting, etc.), there were a number things about the original code that were much improved to my version. Here are a couple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rather than transitioning the &lt;code&gt;left&lt;/code&gt; property to slide the code back and forth, the original author used &lt;code&gt;translateX()&lt;/code&gt;, which is more performant.&lt;/li&gt;
&lt;li&gt;The original author used the &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; element to wrap this widget, which is much more semantically correct than the &lt;code&gt;div&lt;/code&gt; I used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nice touches
&lt;/h2&gt;

&lt;p&gt;Stripe is known for adding a level of polish and detail that most engineering teams can only dream of. Here are just a couple that I noticed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo-elements with transparent-to-opaque background gradients, the text had an appearance of sliding “under” the edges of the widget as it moved back and forth.&lt;/li&gt;
&lt;li&gt;A tasteful &lt;code&gt;box-shadow&lt;/code&gt; was added to augment the 3D effect of the widget.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Any questions or suggestions about my tooling, approach, development style? What would you have done differently? I’d love to hear from you. &lt;a href="https://twitter.com/mjswensen"&gt;Find me on Twitter&lt;/a&gt; or &lt;a href="https://youtu.be/NFOidUvke0k"&gt;leave a comment on the video&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tools
&lt;/h1&gt;

&lt;p&gt;Here are the tools I used in this video:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text editor: &lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt; with theme &lt;a href="https://themer.dev/?colors.dark.shade0=%23171D1D&amp;amp;colors.dark.shade7=%23CDDEDE&amp;amp;colors.dark.accent0=%23F18CB1&amp;amp;colors.dark.accent1=%23B86675&amp;amp;colors.dark.accent2=%23C57B67&amp;amp;colors.dark.accent3=%2300ACBD&amp;amp;colors.dark.accent4=%23208490&amp;amp;colors.dark.accent5=%231A9BA6&amp;amp;colors.dark.accent6=%2332A0AC&amp;amp;colors.dark.accent7=%23FCA188&amp;amp;colors.light.shade0=%23F8FDFE&amp;amp;colors.light.shade7=%2305262D&amp;amp;colors.light.accent0=%23D75971&amp;amp;colors.light.accent1=%23CD2455&amp;amp;colors.light.accent2=%23AA582D&amp;amp;colors.light.accent3=%231D7E66&amp;amp;colors.light.accent4=%2314808C&amp;amp;colors.light.accent5=%230E7481&amp;amp;colors.light.accent6=%234797A7&amp;amp;colors.light.accent7=%23C87D4F&amp;amp;activeColorSet=light&amp;amp;calculateIntermediaryShades.dark=true&amp;amp;calculateIntermediaryShades.light=true"&gt;&lt;em&gt;Right in the Teals&lt;/em&gt;&lt;/a&gt; by &lt;a href="https://github.com/mjswensen/themer"&gt;themer&lt;/a&gt;, and &lt;a href="https://github.com/tonsky/FiraCode"&gt;Fira Code&lt;/a&gt; font&lt;/li&gt;
&lt;li&gt;Browser: &lt;a href="https://brave.com/mjs324"&gt;Brave&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Development server: &lt;a href="https://browsersync.io/"&gt;Browsersync&lt;/a&gt; launched under &lt;a href="https://www.npmjs.com/package/npx"&gt;npx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Application launcher: &lt;a href="https://www.alfredapp.com/"&gt;Alfred&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Color picker: &lt;a href="https://sipapp.io/"&gt;Sip&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>From Electron to Progressive Web App</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Sun, 24 Mar 2019 02:29:00 +0000</pubDate>
      <link>https://dev.to/mjswensen/from-electron-to-progressive-web-app-lhd</link>
      <guid>https://dev.to/mjswensen/from-electron-to-progressive-web-app-lhd</guid>
      <description>&lt;p&gt;In this post I’d like to review my reasoning behind sunsetting an &lt;a href="https://electronjs.org"&gt;Electron&lt;/a&gt;-based application in favor of a &lt;a href="https://developers.google.com/web/progressive-web-apps/"&gt;Progressive Web App (PWA)&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose your platform wisely—you are at the mercy of its maintainers&lt;/li&gt;
&lt;li&gt;URLs are amazing&lt;/li&gt;
&lt;li&gt;You really can't beat the distribution model of the Web&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  themer
&lt;/h1&gt;

&lt;p&gt;First a little context. My application, &lt;a href="https://github.com/mjswensen/themer"&gt;themer&lt;/a&gt;, started off as a command-line tool built on Node.js. It uses a set of colors as input and produces matching themes for various text editors, terminal emulators, and other tools. However, actually &lt;em&gt;choosing&lt;/em&gt; the colors for your personalized theme was tedious and cumbersome. A friend suggested to me that I build a GUI to help ease this process and provide a tighter feedback loop when creating a personalized theme. Given that Electron bundles a Node.js runtime, and that I could therefore easily wrap my CLI in a “native” GUI for all platforms, an Electron app seemed like a perfect fit. themer’s GUI was born:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OrGstfxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/themer-gui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OrGstfxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/themer-gui.png" alt="themer's electron GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The bug
&lt;/h1&gt;

&lt;p&gt;After spending a good amount of time building the GUI and wiring it up to my CLI running in Electron’s Node process, I was able to bundle up the application installers and make them available for download on the &lt;a href="https://github.com/mjswensen/themer-gui/releases"&gt;GitHub releases&lt;/a&gt; page. It wasn’t until after the application was downloaded about 10k times that I discovered &lt;a href="https://github.com/electron/electron/issues/13596"&gt;a really nasty bug&lt;/a&gt; that caused Electron-based applications to crash when using the macOS native color picker:&lt;/p&gt;


  
  


&lt;p&gt;It took nearly eight months to fix that issue, during which the utility of themer’s GUI was drastically reduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;When building for a particular platform or framework, &lt;strong&gt;you are at the mercy of its maintainers&lt;/strong&gt; (of course, if the project is open-source, you can attempt to fix problems yourself, but this isn’t always tenable). Even though Electron is popular, a bug like this wouldn’t have lasted a week in a mainstream Web browser (or likely occurred at all).&lt;/p&gt;

&lt;h1&gt;
  
  
  Shared/persisted state
&lt;/h1&gt;

&lt;p&gt;One requirement for themer’s GUI was that users should be able to save (and ideally share) their themes. Short of some sort of cloud-based service that integrated with the GUI, the best solution was to simply allow themer’s GUI to read and write JSON from the filesystem. One neat thing about Electron is that you can register a custom icon with the operating system for files that match your custom extension:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EBm8d9OV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/themer-file.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EBm8d9OV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/themer-file.png" alt="themer GUI's custom .thmr file extension and icon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I went the whole nine yards and implemented &lt;code&gt;Open&lt;/code&gt;, &lt;code&gt;Save&lt;/code&gt;, &lt;code&gt;Save As...&lt;/code&gt;, friendly prompts to prevent accidental discarding of unsaved changes, etc…. basically everything you’d expect from a traditional desktop program. This worked well enough for persisting theme data, but didn’t allow for easily sharing themes with others.&lt;/p&gt;

&lt;p&gt;On the Web, there’s a better way to share application state: the URL. themer’s new PWA stores all of the theme-relevant state in the URL’s query string, so sharing your theme is as easy as sending a link (like &lt;a href="https://themer.dev/?colors.dark.accent0=%23CA3E5A&amp;amp;colors.dark.accent1=%23D8843E&amp;amp;colors.dark.accent2=%23EBB062&amp;amp;colors.dark.accent3=%2381A559&amp;amp;colors.dark.accent4=%2342ABAB&amp;amp;colors.dark.accent5=%234496CD&amp;amp;colors.dark.accent6=%239770B2&amp;amp;colors.dark.accent7=%23B35D8D&amp;amp;colors.dark.shade0=%2313222E&amp;amp;colors.dark.shade7=%23ACBECC&amp;amp;activeColorSet=dark&amp;amp;calculateIntermediaryShades.dark=true&amp;amp;calculateIntermediaryShades.light=true"&gt;this one&lt;/a&gt;), and saving is as easy as bookmarking (or just using your browser’s history).&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The URL is one of &lt;strong&gt;the Web’s most wonderful and distinguishing features.&lt;/strong&gt; This simple and powerful state/location sharing mechanism is one of my favorite parts about the Web, and is glaringly missing from other platforms.&lt;/p&gt;

&lt;h1&gt;
  
  
  Distribution model
&lt;/h1&gt;

&lt;p&gt;There is a great open-source package available called &lt;a href="https://www.electron.build/"&gt;&lt;code&gt;electron-builder&lt;/code&gt;&lt;/a&gt; that adds support for pushing updates that your app can automatically download in the background. In all, it was relatively easy to configure and get working, but there was always a voice in the back of my mind that said: “I hope the updater code in this version works; if it doesn’t, no one will be able to install updates beyond this point.”&lt;/p&gt;

&lt;p&gt;While it’s still possible to shoot yourself in the foot with a PWA’s &lt;code&gt;ServiceWorker&lt;/code&gt;, more of the cacheing and network code lives in the browser itself, leaving less room for error in user-land.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The distribution model for the Web is unparalleled.&lt;/strong&gt; If you ship a version of your application that is broken, all you have to do is fix it and re-deploy. The platform itself takes care of the rest.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;There are, of course, applications that require the use of Electron (like VS Code for filesystem access, etc.), in which case the choice to build a purely Web-based application is not so simple as it was for my small pet project.&lt;/p&gt;

&lt;p&gt;Electron is a wonderful piece of software, and building an Electron app was an enjoyable experience filled with learning. After having spent some time with it, though, I am more committed than ever to building for my platform of choice: the Web. I can’t wait to see what the future holds for PWAs and Web technologies in general.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://themer.dev"&gt;themer’s new PWA&lt;/a&gt; and let me know what you think.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Zen and the Art of Software Engineering</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Sat, 25 Mar 2017 00:58:00 +0000</pubDate>
      <link>https://dev.to/mjswensen/zen-and-the-art-of-software-engineering-h7p</link>
      <guid>https://dev.to/mjswensen/zen-and-the-art-of-software-engineering-h7p</guid>
      <description>&lt;p&gt;As part of my on-boarding as software engineer at &lt;a href="https://www.nuvi.com/"&gt;Nuvi&lt;/a&gt;, I had the opportunity to read the classic book &lt;em&gt;Zen and the Art of Motorcycle Maintenance: An Inquiry into Values&lt;/em&gt; by Robert M. Pirsig. While the book delves deeply into abstract philosophical concepts, there were a number of concrete principles that I felt had direct application to writing high-quality software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The efficacy of a machine
&lt;/h2&gt;

&lt;p&gt;Pirsig points out that a machine (or system or tool) really has no inherent efficacy; but rather its efficacy is measured by how satisfied we are with it. I thought this was a compelling point of view because it provides us with a useful measuring stick for our software. For example, I could build a beautiful UI that I believe to be streamlined and intuitive, but if our users have different needs, the tool is not effective for them.&lt;/p&gt;

&lt;p&gt;This also applies to developers as they build the software. The book discusses how the true artisan is actively involved in his or her work, being one with the material (the tools, technologies, and code), and has peace of mind when producing good work. If there isn’t peace of mind, it could be a sign of subpar work or an ineffective environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being stuck isn’t a bad thing
&lt;/h2&gt;

&lt;p&gt;An eye-opening point brought up in the book is the idea that being mentally stuck is not a bad thing. It’s so common in day-to-day software work to run into roadblocks, and they are justifiably seen as annoyances that keep engineers from their goal to deliver software. However, these roadblocks provide opportunities for creativity—to think “laterally” as Pirsig calls it—to find new solutions and to innovate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gumption is psychic gasoline
&lt;/h2&gt;

&lt;p&gt;Though nearly 40 years later the word “gumption”—defined as shrewd or spirited initiative and resourcefulness—seems somewhat outdated, it accurately describes the creative fuel we need to produce quality work. We should work to ensure that our proverbial tanks of gumption don’t deplete. Pirsig dedicates a significant amount of time in the book to discuss the sources and drains of gumption. These are the ones that stuck out to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t try to regain gumption by working furiously to make up lost time. It doesn’t work for motorcycles or for software.&lt;/li&gt;
&lt;li&gt;Sometimes making your own motorcycle parts will build gumption. In the software world, I think of this as finding and building the right abstractions in code.&lt;/li&gt;
&lt;li&gt;Ego is a gumption trap. So is anxiety about failure.&lt;/li&gt;
&lt;li&gt;Bad tools are also a gumption trap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating quality work is an all-encompassing act, requiring conscious effort that is more often focused inwardly rather than on the work itself. To adapt Pirsig’s own words: “The real [software] you’re working on is the [software] of yourself.”&lt;/p&gt;

</description>
    </item>
    <item>
      <title>GitHub Pages vs. GitLab Pages</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Fri, 01 Jul 2016 03:59:00 +0000</pubDate>
      <link>https://dev.to/mjswensen/github-pages-vs-gitlab-pages-jjn</link>
      <guid>https://dev.to/mjswensen/github-pages-vs-gitlab-pages-jjn</guid>
      <description>&lt;p&gt;I’ve been a longtime fan of &lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt;. It is a wonderful option for hosting many types of sites. However, recent movements toward a more secure web and &lt;a href="https://github.com/isaacs/github/issues/156"&gt;GitHub’s lack of support for HTTPS on GitHub Pages with a custom domain&lt;/a&gt; prompted me to start looking at other options, including &lt;a href="https://pages.gitlab.io/"&gt;GitLab Pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;GitLab Pages has a lot of great features, many of which are similar to those of GitHub Pages (there’s no question that they are shooting for feature parity with the large incumbent). But there was one problem that was an absolute show-stopper for me: painfully slow build queues. For that reason primarily, I moved back over to GitHub Pages.&lt;/p&gt;

&lt;p&gt;These are the primary differences I found between the two services:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;GitHub Pages&lt;/th&gt;
&lt;th&gt;GitLab Pages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static site generator support&lt;/td&gt;
&lt;td&gt;Jekyll only, no plugins&lt;/td&gt;
&lt;td&gt;Completely customizable build, therefore any SSG is supported (including plugins)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build configuration&lt;/td&gt;
&lt;td&gt;Optional; Jekyll sites will be built automatically, and non-Jekyll sites are published directly. Tests can be set up easily through &lt;a href="https://travis-ci.org/"&gt;travis-ci.org&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Required; without a valid build definition GitLab Pages won’t know how to deploy your site. Setting up the build definition in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; is easy and is very similar to setting up &lt;code&gt;.travis.yml&lt;/code&gt; for GitHub Pages project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build queue time&lt;/td&gt;
&lt;td&gt;Almost always instantaneous, both on the GitHub Pages side and the Travis CI side&lt;/td&gt;
&lt;td&gt;Excruciatingly slow; of the several builds I triggered on my site, the average wait time was a couple of hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support for HTTPS&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/isaacs/github/issues/156"&gt;Partial&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://about.gitlab.com/2016/04/07/gitlab-pages-setup/#custom-domains"&gt;Yes&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN support&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/blog/1715-faster-more-awesome-github-pages"&gt;Out-of-the-box&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not provided by GitLab Pages but can be configured with free tiers of popular CDN providers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GitLab Pages has a lot of great features. And the GitLab.com service is generally fantastic, especially given that it’s free. One of my favorite parts is that they offer free build instances (they call them “runners”) as part of the GitLab.com offering (and the CI is built right into the core product—another plus). The only issue is that since the runners are free, they are overworked and the build queues can be hours long (at the time of writing, at least). While it’s still a wonderful service, the delayed builds means a feedback loop that is too slow for feature development and a production deploy time that is too unpredictable.&lt;/p&gt;

&lt;p&gt;That slowness and the need to configure CDN myself outweighed my desire to enable TLS on my site for now. And given GitHub’s track record, I also have a sneaking suspicion that TLS support on GitHub Pages is just around the corner. In which case I’ll be extra glad I stayed.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Static Sites Are Awesome</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Sat, 19 Sep 2015 21:41:00 +0000</pubDate>
      <link>https://dev.to/mjswensen/static-sites-are-awesome-2dg2</link>
      <guid>https://dev.to/mjswensen/static-sites-are-awesome-2dg2</guid>
      <description>&lt;p&gt;When creating landing pages, content sites, or blogs, content management systems shouldn’t be the first tool web developers reach for. Because of the many benefits that come with the simplicity of static sites, they are not only a viable alternative, they are a &lt;em&gt;more practical&lt;/em&gt; alternative. And with the popularity of static site generators rapidly on the rise, creating and maintaining static websites is easier than ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why static sites?
&lt;/h2&gt;

&lt;p&gt;For certain types of content, static websites excel over content management systems in lots of areas, including performance, decreased server maintenance, site maintainability, and capacity for version-controlled content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;In today’s world where site performance is of utmost importance, the most compelling reason to move toward static websites is the performance benefit. Content management systems make use of a database, and reading from the database to serve page content incurs a performance cost. Static sites make no use of a database—all the content is baked right into the final HTML code. The performance win comes from the fact that modern webservers (like Apache or nginx) are heavily optimized to serve such static files extremely quickly.&lt;/p&gt;

&lt;p&gt;In a nutshell, with a CMS, the burden of constructing a web page is placed on the server, at the time the page is requested. With a static site, that burden is moved to when the site is assembled before it is deployed. Many programmers will agree that, from a user experience perspective, it is better to have as much processing occur as possible at compile time rather than at run time. This is the approach that static site generators take.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7zV1Czxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/cms-vs-static-website-generator.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7zV1Czxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/cms-vs-static-website-generator.png" alt="Content management systems performance vs. static site generators performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Server maintenance
&lt;/h3&gt;

&lt;p&gt;Another big advantage for static sites is the reduced amount of required server maintenance. With a typical CMS like Wordpress or Drupal, sysadmins will need to install and configure a scripting language runtime (like PHP), a webserver (like Apache), and a database server (like MySQL). Security patches and periodic software updates will also need to be run for each of these. With static sites, only a webserver to serve the static files is required. (It is also arguable that security of the server is improved with this simpler setup.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintainability
&lt;/h3&gt;

&lt;p&gt;Some scoff at the idea of building a static website because it sounds to the unfamiliar ear like writing static webpages individually by hand, which of course is a cumbersome task that content management systems were designed to overcome. However, the “static” part is simply the compile target. Static site generators allow web developers to build sites with familiar programming constructs, like DRY, logic and flow control, templating, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Version control
&lt;/h3&gt;

&lt;p&gt;Without the dependency on a database system, it is also much easier to keep the site’s content under revision control, depending on how the developer has chosen to deploy and publish to the site. This also means that content is more easily synced with code between different environments (i.e., development and production).&lt;/p&gt;

&lt;h2&gt;
  
  
  Concerns with static sites
&lt;/h2&gt;

&lt;p&gt;The obvious concern with static sites is that they cannot serve dynamic content. However, there are ways to add common dynamic elements to the site without the need of a full-blown CMS. For example, the popular service &lt;a href="https://disqus.com/"&gt;Disqus&lt;/a&gt; provides a fully featured commenting system for a page, simply by adding a small JavaScript snippet. There are also services like Firebase that allow client-side applications to communicate directly with a database.&lt;/p&gt;

&lt;p&gt;There are, of course, many cases where a static site is inappropriate. Any service where users log in to access their content is a common example. In these cases, one might consider using a static site for blogs or landing pages and minimize the the scope of the backend code to the core of the application itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying and publishing content to static sites
&lt;/h2&gt;

&lt;p&gt;Static sites can be deployed in a variety of ways. One of the simplest ways is to take the compiled output of the static site generator and drop it into an Amazon S3 bucket. As S3 (and similar services) are preconfigured to serve static files, there is no server setup required. Take one more step to put that bucket behind a service like Amazon CloudFront, and the site’s files are now served over CDN, with minimal effort!&lt;/p&gt;

&lt;p&gt;Another noteworthy mechanism that works great for open-source sites is &lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt;, a free service offered by GitHub. GitHub Pages integrates automatically with one of the most popular static site generators, Jekyll, and so deploying and pushing updates to a site is as simple as &lt;code&gt;git push&lt;/code&gt;. &lt;a href="https://github.com/blog/1715-faster-more-awesome-github-pages"&gt;GitHub Pages are also served via CDN.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also great workflows that allow non-technical people to publish to a static site. For example, &lt;a href="https://www.contentful.com/"&gt;Contentful&lt;/a&gt; is a content management service that has fast, read-only APIs for retrieving content. Non-tech writers can log into Contentful to create the content, and the static site generator could be configured to receive updates from the API, compile the site, and deploy the changes automatically. A similar workflow based on syncing a Dropbox folder of markdown files with a static site generator could also work well. With the flexibility of static sites, teams can find a publishing paradigm that works well for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Static websites are a practical tool in a web developer’s tool belt. The simplicity of a static website can be a welcome replacement for complex content management systems.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Blissful Static Site Development with Jekyll, BrowserSync, and tmux</title>
      <dc:creator>Matt Swensen</dc:creator>
      <pubDate>Sat, 20 Jun 2015 05:44:34 +0000</pubDate>
      <link>https://dev.to/mjswensen/blissful-static-site-development-with-jekyll-browsersync-and-tmux-4ei3</link>
      <guid>https://dev.to/mjswensen/blissful-static-site-development-with-jekyll-browsersync-and-tmux-4ei3</guid>
      <description>&lt;p&gt;I was recently listening to the &lt;a href="http://shoptalkshow.com/"&gt;Shop Talk Show podcast&lt;/a&gt; where &lt;a href="https://css-tricks.com/"&gt;Chris Coyier&lt;/a&gt; shared a submission from a listener that outlined the development and deployment setup for her personal website. It involved using Dropbox to sync blog posts to a static site generator on her server and sounded pretty slick. I thought I would add to the conversation by sharing my setup for developing this site. These are the technologies that are used in concert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Pages&lt;/li&gt;
&lt;li&gt;Jekyll&lt;/li&gt;
&lt;li&gt;BrowserSync&lt;/li&gt;
&lt;li&gt;tmux&lt;/li&gt;
&lt;li&gt;iTerm2&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Here is a visual overview of the development setup, created with &lt;a href="https://www.lucidchart.com/"&gt;Lucidchart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WNPmHKG9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/blissful-static-site-process.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WNPmHKG9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/blissful-static-site-process.png" alt="Visual representation of static site development setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Further explanation of each component is below.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Pages
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; is just about the easiest way to deploy a basic website these days. If you have a GitHub account (free), the process is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a repository with a special name: &lt;code&gt;&amp;lt;your username&amp;gt;.github.io&lt;/code&gt; (for example, mine is &lt;a href="https://github.com/mjswensen/mjswensen.github.io"&gt;&lt;code&gt;mjswensen.github.io&lt;/code&gt;&lt;/a&gt; for this website).&lt;/li&gt;
&lt;li&gt;Add your website files (&lt;code&gt;index.html&lt;/code&gt;, etc.) to the repository.&lt;/li&gt;
&lt;li&gt;Commit and push. Within a few minutes (usually just seconds), your site will be live at &lt;code&gt;http://&amp;lt;your username&amp;gt;.github.io/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want a custom domain instead of the default, it’s easy to &lt;a href="https://help.github.com/articles/setting-up-a-custom-domain-with-github-pages/"&gt;set that up&lt;/a&gt;, too.&lt;/p&gt;

&lt;p&gt;The best part? It’s &lt;em&gt;fast&lt;/em&gt;. &lt;a href="https://github.com/blog/1715-faster-more-awesome-github-pages"&gt;GitHub distributes the content on GitHub Pages via CDN&lt;/a&gt; out of the box, so visitors to your website download your content quickly no matter where they are. One of my recent posts happened to be trending on &lt;a href="https://news.ycombinator.com/item?id=9113474"&gt;Hacker News&lt;/a&gt; one morning and that page had about 7,500 views over the course of a few hours; the site was still responsive as ever during that time, despite the drastically increased load.&lt;/p&gt;

&lt;p&gt;And it’s &lt;strong&gt;free&lt;/strong&gt;. You really can’t beat that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jekyll
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; is a static site generator, which—as the name suggests—makes creating static HTML sites a breeze. Jekyll is one of the most popular static site generators. This is due in part to the fact that &lt;em&gt;it integrates with GitHub Pages&lt;/em&gt;. If you build your site with Jekyll, GitHub will detect that your site is a Jekyll site and will compile it for you when you push (as outlined above). So for development, one can install Jekyll and build the site locally, push and have GitHub build the live site, and not have to worry about keeping the compiled site files in source control or uploading them via FTP.&lt;/p&gt;

&lt;p&gt;There are a lot of advantages to moving away from content management systems and toward static sites where possible. Increased site performance is the primary one (content management systems usually involve reading from a database, whereas static sites are simply files that can be quickly served from webservers like &lt;a href="http://nginx.org/"&gt;nginx&lt;/a&gt; that have been heavily optimized for that task). It’s also nice to keep content under version control rather than trying to sync databases across different environments.&lt;/p&gt;

&lt;p&gt;(Sidenote: Among Jekyll’s many features, one of the most convenient is its native support for &lt;a href="http://sass-lang.com/"&gt;Sass&lt;/a&gt;. Sass makes writing CSS painless and fun, and the fact that Jekyll will compile the Sass sources automatically is a big plus.)&lt;/p&gt;

&lt;h2&gt;
  
  
  BrowserSync
&lt;/h2&gt;

&lt;p&gt;While Jekyll by default has a built-in server enabled for testing as you develop, &lt;a href="http://www.browsersync.io/"&gt;BrowserSync&lt;/a&gt; has some incredible features for developing for the modern web:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live reloading when sources change&lt;/li&gt;
&lt;li&gt;Synced page viewing and scrolling across multiple devices or browsers simultaneously&lt;/li&gt;
&lt;li&gt;Great debugging features&lt;/li&gt;
&lt;li&gt;Automatically go to local site in new Chrome tab on launch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, while Jekyll watches my source files for changes and generates the site automatically, BrowserSync watches Jekyll’s output directory and pushes changes to the browser automatically. It makes for a great development workflow!&lt;/p&gt;

&lt;h2&gt;
  
  
  tmux
&lt;/h2&gt;

&lt;p&gt;While starting Jekyll and BrowserSync is pretty simple via the command line, it is the same two commands and requires two terminal windows each time I want to write a new blog post or develop the site. This is where &lt;a href="http://tmux.github.io/"&gt;tmux&lt;/a&gt; comes in. tmux is a terminal multiplexer, which means that it allows for text-based windows and split panes within a single terminal window, among many other fantastic features.&lt;/p&gt;

&lt;p&gt;The best part about tmux is that it is easily scriptable. So for developing this site, I’ve written a Bash script that sets up my tmux session, configures the windows and panes as I like them, and starts Jekyll and BrowserSync in separate panes. The script is pretty simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;SESSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mjswensen"&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/Users/mjswensen/Projects/other/mjswensen.github.io"&lt;/span&gt;
&lt;span class="nv"&gt;OUTPUT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;&lt;span class="s2"&gt;/_site"&lt;/span&gt;

tmux new-session &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"site"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;

tmux split-window &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;:0 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;
tmux split-window &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;:0.1 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;$OUTPUT_PATH&lt;/span&gt;

tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;:0.0 &lt;span class="s2"&gt;"git status"&lt;/span&gt; enter
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;:0.1 &lt;span class="s2"&gt;"jekyll build --watch"&lt;/span&gt; enter
tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;:0.2 &lt;span class="s2"&gt;"browser-sync start --server --files &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;*.css, *.html&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; enter

tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-pane&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;:0.0

tmux &lt;span class="nt"&gt;-CC&lt;/span&gt; attach-session &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$SESSION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  iTerm2
&lt;/h2&gt;

&lt;p&gt;Finally, I use &lt;a href="http://iterm2.com/"&gt;iTerm2&lt;/a&gt; in place of the standard Terminal.app for Mac. iTerm2 has some amazing features and is a popular choice for terminal emulation among Mac users. The reason I like iTerm2 so much is for its deep tmux integrations. While tmux brings some great features to the terminal, certain common tasks—like switching between windows or copying text, or scrolling—involve cumbersome keyboard commands and workarounds. iTerm2, when attached to a tmux session with the special &lt;code&gt;-CC&lt;/code&gt; command-line flag (as seen in the script above), &lt;em&gt;converts tmux windows and panes to native iTerm2 tabs and panes&lt;/em&gt;. It sounds simple, but it really makes all the difference. It really is a great solution for tmux fans.&lt;/p&gt;

&lt;p&gt;In the end, this is what the iTerm2 window looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HZcTbVlR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/blissful-static-site-iterm2-screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HZcTbVlR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mjswensen.com/blog/images/blissful-static-site-iterm2-screenshot.png" alt="Screenshot of iTerm2 connected to tmux session"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
