<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Posts about Internet</title><link>https://chriswarrick.com/</link><atom:link href="https://chriswarrick.com/blog/tags/cat_internet.xml" rel="self" type="application/rss+xml" /><description>A rarely updated blog, mostly about programming.</description><lastBuildDate>Sun, 09 Feb 2020 15:30:00 GMT</lastBuildDate><generator>https://github.com/Kwpolska/YetAnotherBlogGenerator</generator><item><title>When HTML is not enough: a tale of the &lt;datalist&gt; element</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/</link><pubDate>Sun, 09 Feb 2020 15:30:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/</guid><description>
HTML 5.0 was finalized in 2014 (and its drafts were published even earlier), and with it came the &amp;lt;datalist&amp;gt; element.  It’s
2020, and even though it might look like a good replacement for custom
autocomplete widgets, browser issues made me get rid of it.
</description><content:encoded><![CDATA[
<p>HTML 5.0 was finalized in 2014 (and its drafts were published even earlier), and with it came the <code class="docutils literal">&lt;datalist&gt;</code> element.  It’s
2020, and even though it might look like a good replacement for custom
autocomplete widgets, browser issues made me get rid of it.</p>



<p>I’ve built a web app to help me track my expenses. The app is written in Django,
and <a class="reference external" href="https://github.com/Kwpolska/django-expenses">it’s open source</a>. One of
the goals was to have a simple codebase with limited external JS dependencies,
as well as basic usability with JS disabled. This is partially to facilitate
learning of standard DOM manipulation routines and TypeScript.</p>
<p>The JS/TS bits are called the <em>Scripting Enhancements</em> to reflect their nature.
The biggest items are an interactive bill editor (a table with add/edit/remove
operations, that submits its data as a regular HTML POST <code class="docutils literal">&lt;form&gt;</code>) and an
autocomplete framework (used by the bill editor in an advanced way, and by
other screens in the app with a more basic featureset). The autocomplete
framework is exactly what you’d expect: point it at an input field and a URL,
and keypresses lead to the URL being queried for previous values for this
field, which are displayed as possible values to the user to save typing.</p>
<section id="autocomplete-with-html-5-the-datalist-tag">
<h1>Autocomplete with HTML 5: the &lt;datalist&gt; tag</h1>
<p>But how to display the options to the user? Most people would display a
<code class="docutils literal">position: absolute</code> box with links/buttons, throw in some more logic around
the focus and blur events, and call it a day. There are tons of ready-made
solutions that do all that for you, although most of them are terrible. But!
HTML 5 introduced a <code class="docutils literal">&lt;datalist&gt;</code> tag. And it looks like everything you could
need. You link a <code class="docutils literal">&lt;datalist&gt;</code> tag to an <code class="docutils literal">&lt;input&gt;</code> and it shows matching
options in an autocomplete-style box.  In fact, here’s a simple demo, in case
your browser supports it:</p>
<div class="card mb-3 text-center">
    <div class="card-body">
        <label for="dldemo" class="ml-1 mr-1">Favorite programming language:</label>
        <input class="form-control d-inline-block ml-1 mr-1" style="width: auto" placeholder="Start typing…" list="dldemolist" id="dldemo">
        <datalist id="dldemolist">
            <option value="Swift">
            <option value="Rust">
            <option value="Ruby">
            <option value="Python">
            <option value="PHP">
            <option value="Kotlin">
            <option value="JavaScript">
            <option value="Java">
            <option value="Go">
            <option value="C++">
            <option value="C#">C Sharp</option>
            <option value="C">
        </option></datalist>
    </div>
    <div class="card-footer"><a href="https://chriswarrick.com/listings/datalist/datalist-demo.html.html">View demo source</a></div>
</div><p>Now, here are a few takeaways from that demo:</p>
<ol class="arabic simple">
<li><p>Options are displayed in the same order as in the <code class="docutils literal">&lt;datalist&gt;</code> tag in the
source, this list was sorted reverse-alphabetically in the source, and
that’s how it appears in the source.</p></li>
<li><p>The list is filtered case-insensitively based on user-input substrings. In
Chrome, Firefox and Safari, the substring can appear at any point in the
string.  But in Edge (old Microsoft engine), it looks only at the beginning
of the string.</p></li>
<li><p>Some browsers show an arrow on the field to show the entries, sometimes
double-clicking opens the list.</p></li>
<li><p>The entry for C# is as follows: <code class="docutils literal">&lt;option <span class="pre">value=&quot;C#&quot;&gt;C</span> <span class="pre">Sharp&lt;/option&gt;</span></code>.
Chrome displays it on as <span class="raw-html">“<strong>C#</strong> <small>C
Sharp</small>”</span> (on two lines), Safari shows only “C#”, Firefox and Edge
show “C Sharp”. Selecting the option always inputs C#.</p></li>
<li><p>Mobile Safari does not expand the list by default, but displays some of the
options above the keyboard (as typing predictions). You can click on the
arrow to display all the options in a <a class="reference external" href="https://developer.apple.com/design/human-interface-guidelines/ios/controls/pickers/">scrolling picker</a>.</p></li>
<li><p>Chrome on Android displays it the same way as on desktop (drop-down list).</p></li>
</ol>
<p>This demo uses static, hardcoded data. Doing that for the Expenses app would
be terrible for performance — that would waste bandwidth, force the browser to
parse a fairly long list, and it could easily overload the browser when it
tries to expand the list. But wiring it up to a <code class="docutils literal">fetch()</code> call to a REST API
should not be hard, and browsers work correctly when the datalist changes.</p>
</section>
<section id="an-emoji-hack">
<h1>An emoji hack</h1>
<p>One of the features I needed was to make the auto-complete fill out more than
one field at once. Well, <code class="docutils literal">&lt;datalist&gt;</code> has no specific support for that. It only
supports showing a list and putting the value in the input box it’s connected
to. But choosing something from the list fires the usual <code class="docutils literal">input</code> event. I
opted to do this: show every entry with a sparkles emoji (✨) in front, with the
two other fields also inside this string, delimited by other emoji, and then
catch the <code class="docutils literal">input</code> event.  If the field beigns with ✨, then use a regex to go
from one emoji-delimited string to three, and place the correct strings in
three input boxes (while also removing the sparkles from the first field).</p>
<p>Yes, it’s a hack. But it’s pretty okay appearance-wise, and it does work. It
wouldn’t have worked so well in Edge, but I didn’t even know about this
behavior before writing this blog post, and the initial sparkles emoji could be
dropped and I could still make it work.</p>
</section>
<section id="works-on-mobile-yes-except">
<h1>Works on mobile? Yes, except…</h1>
<p>I went on and deployed the <code class="docutils literal">&lt;datalist&gt;</code>-based autocomplete to my site. It
looked good, worked fine. To use the thing on mobile, I’ve got a special
launcher app. Its main reason for existence? I want a home screen icon, but
Chrome only allows progressive web apps to do that (and that’s busywork I don’t
feel like doing), and back then, Firefox (which has no such restrictions) did
not support <code class="docutils literal">&lt;datalist&gt;</code> on Android.  The app is fairly simple, with a
standard WebView widget and a slide-out navigation drawer, and a few other nice
things, and it’s 120 SLOC of Kotlin.</p>
<p>But then, I bought a new phone, and with it, upgraded from Android 7 to 9. And
I hit a bug in Chrome, which is still not fixed. The bug?
<a class="reference external" href="https://bugs.chromium.org/p/chromium/issues/detail?id=949555">HTML datalist doesn’t work on Android 8 or higher in WebView</a>.</p>
<p>Oh. We’ve got a bit of a problem. Firefox still didn’t seem to support
<code class="docutils literal">&lt;datalist&gt;</code>. But there’s one more way to make an app show a webpage: Custom
Tabs. This is a feature you’ve probably seen around Android, and it’s somewhere
in between. The app gets minimum control over the appearance of the toolbar,
but the “real” web browser is responsible for rendering the page. Chrome in a
Custom Tab supports <code class="docutils literal">&lt;datalist&gt;</code>. So I built a small app to do what I wanted.</p>
<p>There was just one minor thing to fix. My default browser on mobile is <a class="reference external" href="https://support.mozilla.org/en-US/kb/focus">Firefox
Focus</a>. The main features of
Focus are tracking protection, content blocking, and storing zero
history and cookies (permanent incognito mode with one-click clearing). This is
perfect for clicking random links, especially since I hate Chrome’s insistence
on showing webpages you visited 5 years ago once when autocompleting URLs.
(Chrome is my secondary browser on mobile; on desktop, I almost always have an
incognito window open.)</p>
<p>Why is Focus relevant to this story? One, it (still) does not support the tag.
Two, the default browser is also the provider of the Custom Tabs. Which is
great for my web-browsing habits, but won’t solve the problem.  Fortunately,
it’s just a one-line change to send the intent directly to Chrome. The entire
thing is less than 30 lines long. You can see the full <a class="reference external" href="https://chriswarrick.com/listings/android-chrome-custom-tabs/CustomTabsActivity.java.html">CustomTabsActivity.java</a> file, but the relevant bits are below.</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/#rest_code_bcfa6063001f4807a06ebb53b9d81b94-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="rest_code_bcfa6063001f4807a06ebb53b9d81b94-1" name="rest_code_bcfa6063001f4807a06ebb53b9d81b94-1"></a><span class="n">CustomTabsIntent</span><span class="p">.</span><span class="na">Builder</span><span class="w"> </span><span class="n">builder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">CustomTabsIntent</span><span class="p">.</span><span class="na">Builder</span><span class="p">();</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/#rest_code_bcfa6063001f4807a06ebb53b9d81b94-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="rest_code_bcfa6063001f4807a06ebb53b9d81b94-2" name="rest_code_bcfa6063001f4807a06ebb53b9d81b94-2"></a><span class="c1">// Optionally, configure appearance and buttons on toolbar.</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/#rest_code_bcfa6063001f4807a06ebb53b9d81b94-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="rest_code_bcfa6063001f4807a06ebb53b9d81b94-3" name="rest_code_bcfa6063001f4807a06ebb53b9d81b94-3"></a><span class="n">CustomTabsIntent</span><span class="w"> </span><span class="n">intent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">builder</span><span class="p">.</span><span class="na">build</span><span class="p">();</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/#rest_code_bcfa6063001f4807a06ebb53b9d81b94-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="rest_code_bcfa6063001f4807a06ebb53b9d81b94-4" name="rest_code_bcfa6063001f4807a06ebb53b9d81b94-4"></a><span class="c1">// Force browser to Chrome instead of system default.</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/#rest_code_bcfa6063001f4807a06ebb53b9d81b94-5"><code data-line-number="5"></code></a></td><td class="code"><code><a id="rest_code_bcfa6063001f4807a06ebb53b9d81b94-5" name="rest_code_bcfa6063001f4807a06ebb53b9d81b94-5"></a><span class="n">intent</span><span class="p">.</span><span class="na">intent</span><span class="p">.</span><span class="na">setPackage</span><span class="p">(</span><span class="s">&quot;com.android.chrome&quot;</span><span class="p">);</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2020/02/09/when-html-is-not-enough-a-tale-of-the-datalist-element/#rest_code_bcfa6063001f4807a06ebb53b9d81b94-6"><code data-line-number="6"></code></a></td><td class="code"><code><a id="rest_code_bcfa6063001f4807a06ebb53b9d81b94-6" name="rest_code_bcfa6063001f4807a06ebb53b9d81b94-6"></a><span class="n">intent</span><span class="p">.</span><span class="na">launchUrl</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"> </span><span class="n">Uri</span><span class="p">.</span><span class="na">parse</span><span class="p">(</span><span class="s">&quot;https://chriswarrick.com/&quot;</span><span class="p">));</span>
</code></td></tr></table></div><p>It seems to work well, the list is displayed, and it can be used to input
stuff, the emoji hack works too.</p>
<p>There was one more bug with Chrome on Android. Typing a character sometimes led to
it appearing twice: I typed <em>A</em>, the hints appeared, then the text box started
showing <em>AA</em>, and my hints disappeared. I can’t reproduce it right now, but
that also made the entire flow just annoying.</p>
</section>
<section id="aftermath">
<h1>Aftermath</h1>
<p>With all the browser bugs, support issues, and various glitches, I decided to
build an autocomplete widget of my own. I took the CSS from Bootstrap 4, and
used Popper.js to do the positioning. It looks and works better, has keyboard
support, and is definitely less hacky (the emoji is still there, because they
look good, but my entries know the original object they were made from and can
just tell the handler to use that instead of using regex). And it beats many of
the autocomplete widgets out there, because they often fail when you hold the
mouse a bit longer; also, it can reposition itself to the top if there’s more
space. All that in just 198 SLOC of TypeScript. (I also discovered a bug in my
code that made it work a bit worse, fixing it for the old implementation would
still not fix the other issues.)</p>
<p>What’s the moral of the story? Even though HTML 5 has been a standard for many
years, browser support for the new tags still seems to be an issue. And
sometimes, it’s better to just put in the extra work and build a good UI on
your own, instead of trusting the browser to do it right.</p>
<p>The same applies to other “new” HTML 5 form elements.  <code class="docutils literal">&lt;input <span class="pre">type=&quot;date&quot;&gt;</span></code>
is not supported in desktop Safari, and is fairly ugly in desktop Firefox and
Chrome.  It displays the standard OS picker on mobile, which gets you a
calendar on Android, but a <a class="reference external" href="https://developer.apple.com/design/human-interface-guidelines/ios/controls/pickers/">scrolling picker</a> on iOS.
<code class="docutils literal"><span class="pre">datetime-local</span></code> is currently Chrome-only.  <code class="docutils literal">month</code> lets you click on a day
and end up with an entire month selected in Chrome.  A custom component with
JavaScript would be far more consistent and often easier to use.</p>
</section>
]]></content:encoded><category>Internet</category><category>HTML</category><category>HTML5</category><category>JavaScript</category><category>TypeScript</category><category>web development</category></item><item><title>Modern Web Development: where you need 500 packages to build Bootstrap</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/</link><pubDate>Fri, 15 Feb 2019 18:00:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/</guid><description>
This humble blog is written by an old-school developer who sometimes does web stuff. An attempt to customize the Bootstrap CSS theme requires 50 MB of node_modules, over 500 packages, and comes with a bit of frustration at stupid tools and terrible documentation.
</description><content:encoded><![CDATA[
<p>This humble blog is written by an old-school developer who sometimes does web stuff. An attempt to customize the Bootstrap CSS theme requires 50 MB of node_modules, over 500 packages, and comes with a bit of frustration at stupid tools and terrible documentation.</p>



<p>You might notice that this website is based on Bootstrap. You might also notice it’s been heavily customized, especially if you’re browsing in the (currently default) Dark Mode. Back in Bootstrap v3 days, the task was accomplished by <a class="reference external" href="https://getbootstrap.com/docs/3.4/customize/">a simple online tool</a> that required no local installs. Bootstrap 4 changed the landscape: now you need to manually compile Sass. Moreover, Autoprefixer is required to make the CSS usable by web browsers.</p>
<p>Now, when it comes to web development, I believe the old ways were better. Back when nobody thought to make a client-side-JS-based blog or pastebin, and only apps that needed interactivity were JS-first. Gmail is a good example of that, although they <em>still</em> offer a <a class="reference external" href="https://support.google.com/mail/answer/15049?hl=en">basic HTML view</a> and it works good — in fact, I suppose it might be less buggy than the JS-ladden version. (A lot of single-page apps like to randomly glitch out in my experience.)</p>
<p>I still remember the days when all that one had to do is <code class="docutils literal">java <span class="pre">-jar</span> yuicompressor.jar style.css &gt; style.min.css</code>. Then Less and Sass became more popular — and that’s good. The ability to use variables and functions makes it possible to produce well-organized stylesheets. The idea of Autoprefixer is also fine, humans should not waste their time with browser-specific prefixes for experimental features, that can be neatly automated.</p>
<p>But to use all these fancy tools, glue code is necessary. Autoprefixer is (mainly server-side) JS-only, Sass is currently Node or Dart, minifier tools are available in many languages.</p>
<section id="attempt-0-no-js-stuff-no-node-modules">
<h1>Attempt 0: no JS stuff, no node_modules</h1>
<p>I installed a Sass compiler. There are web services like cssminifier.com that can be easily used with curl in a Bash script. Autoprefixer has a webpage that lets you use the service without installing it as well. The catch is, the code runs locally in your web browser. Automating a web browser requires some effort. I decided to leave this part un-automated. Here is the Bash script I hacked together (with some messages removed):</p>
<div class="code"><pre class="code sh"><a id="rest_code_dafa69619b654712819aa7d0fa12081f-1" name="rest_code_dafa69619b654712819aa7d0fa12081f-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-1"></a>sass<span class="w"> </span>bootstrap-kw.scss<span class="w"> </span>&gt;<span class="w"> </span>bootstrap.noprefix.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-2" name="rest_code_dafa69619b654712819aa7d0fa12081f-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-2"></a>sass<span class="w"> </span>bootstrap-kw-dark.scss<span class="w"> </span>&gt;<span class="w"> </span>bootstrap-dark.noprefix.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-3" name="rest_code_dafa69619b654712819aa7d0fa12081f-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-3"></a>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-4" name="rest_code_dafa69619b654712819aa7d0fa12081f-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-4"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;Go to https://autoprefixer.github.io/.&quot;</span>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-5" name="rest_code_dafa69619b654712819aa7d0fa12081f-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-5"></a>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-6" name="rest_code_dafa69619b654712819aa7d0fa12081f-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-6"></a>pbcopy<span class="w"> </span>&lt;<span class="w"> </span>bootstrap.noprefix.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-7" name="rest_code_dafa69619b654712819aa7d0fa12081f-7" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-7"></a><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;(light) Paste the clipboard contents and copy the output, then press Enter.&quot;</span>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-8" name="rest_code_dafa69619b654712819aa7d0fa12081f-8" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-8"></a><span class="nb">read</span><span class="w"> </span>temp
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-9" name="rest_code_dafa69619b654712819aa7d0fa12081f-9" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-9"></a>pbpaste<span class="w"> </span>&gt;<span class="w"> </span>assets/css/bootstrap.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-10" name="rest_code_dafa69619b654712819aa7d0fa12081f-10" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-10"></a>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-11" name="rest_code_dafa69619b654712819aa7d0fa12081f-11" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-11"></a>sleep<span class="w"> </span><span class="m">1</span>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-12" name="rest_code_dafa69619b654712819aa7d0fa12081f-12" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-12"></a>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-13" name="rest_code_dafa69619b654712819aa7d0fa12081f-13" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-13"></a>pbcopy<span class="w"> </span>&lt;<span class="w"> </span>bootstrap-dark.noprefix.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-14" name="rest_code_dafa69619b654712819aa7d0fa12081f-14" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-14"></a><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;( dark) Paste the clipboard contents and copy the output, then press Enter.&quot;</span>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-15" name="rest_code_dafa69619b654712819aa7d0fa12081f-15" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-15"></a><span class="nb">read</span><span class="w"> </span>temp
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-16" name="rest_code_dafa69619b654712819aa7d0fa12081f-16" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-16"></a>pbpaste<span class="w"> </span>&gt;<span class="w"> </span>assets/css/bootstrap-dark.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-17" name="rest_code_dafa69619b654712819aa7d0fa12081f-17" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-17"></a>
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-18" name="rest_code_dafa69619b654712819aa7d0fa12081f-18" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-18"></a>curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-s<span class="w"> </span>--data-urlencode<span class="w"> </span><span class="s1">&#39;input@assets/css/bootstrap.css&#39;</span><span class="w"> </span>https://cssminifier.com/raw<span class="w"> </span>&gt;<span class="w"> </span>assets/css/bootstrap.min.css
<a id="rest_code_dafa69619b654712819aa7d0fa12081f-19" name="rest_code_dafa69619b654712819aa7d0fa12081f-19" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dafa69619b654712819aa7d0fa12081f-19"></a>curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span>-s<span class="w"> </span>--data-urlencode<span class="w"> </span><span class="s1">&#39;input@assets/css/bootstrap-dark.css&#39;</span><span class="w"> </span>https://cssminifier.com/raw<span class="w"> </span>&gt;<span class="w"> </span>assets/css/bootstrap-dark.min.css
</pre></div>
<p>The “manual copy” solution was inconvenient, but it worked.</p>
<p>Well, most of the time Some lags/glitches with the clipboard meant that sometimes, files had the incorrect content. So, I wanted to fix it, and build it in a more modern, JS-y way. The way Bootstrap does it is a lot of shell commands (that run various Node tools). I don’t feel like building this pipeline with Bash, it would feel fragile. Let’s do it the JS way.</p>
</section>
<section id="attempt-1-webpack">
<h1>Attempt 1: webpack</h1>
<p>I’ve used webpack for <a class="reference external" href="https://github.com/Kwpolska/django-expenses/blob/master/ts/webpack.config.js">another project of mine</a>. It was okay, and it did the job (namely, compiling TypeScript into browser-usable JS).</p>
<p>I wanted to give it a try for this one. I googled “webpack sass”. The first result was <a class="reference external" href="https://github.com/webpack-contrib/sass-loader">sass-loader</a>. The pipeline for it was:</p>
<div class="code"><pre class="code javascript"><a id="rest_code_7bad4f65d96d4c5187951f660174631c-1" name="rest_code_7bad4f65d96d4c5187951f660174631c-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_7bad4f65d96d4c5187951f660174631c-1"></a><span class="s2">&quot;style-loader&quot;</span><span class="p">,</span><span class="w"> </span><span class="c1">// creates style nodes from JS strings</span>
<a id="rest_code_7bad4f65d96d4c5187951f660174631c-2" name="rest_code_7bad4f65d96d4c5187951f660174631c-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_7bad4f65d96d4c5187951f660174631c-2"></a><span class="s2">&quot;css-loader&quot;</span><span class="p">,</span><span class="w"> </span><span class="c1">// translates CSS into CommonJS</span>
<a id="rest_code_7bad4f65d96d4c5187951f660174631c-3" name="rest_code_7bad4f65d96d4c5187951f660174631c-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_7bad4f65d96d4c5187951f660174631c-3"></a><span class="s2">&quot;sass-loader&quot;</span><span class="w"> </span><span class="c1">// compiles Sass to CSS, using Node Sass by default</span>
</pre></div>
<p>Let’s recap. Someone thought that the right way to do CSS is to use JS imports.</p>
<p>Yes. <code class="docutils literal">import <span class="pre">&quot;./style.css&quot;;</span></code> in a JS file. So that your fancy build tool knows about CSS.</p>
<p>Webpack wasn’t the right tool for my project, but even if I had JS code there, <strong>WHY WOULD I MENTION STYLESHEETS IN MY JS CODE?!</strong> Webpack’s website also lists .jpg and .png assets, are they meant to be imported in JS as well? This is absurd.</p>
<p>Going back to googling “webpack sass”… The next two results were Medium posts. The stupidity of Medium as a blog platform notwithstanding, one of the posts was from 2017, referring to webpack 2. The next post was a year older, a completely unreadable mess, and it was for webpack 4. That’s not helpful in any way.</p>
</section>
<section id="attempt-2-gulp">
<h1>Attempt 2: Gulp</h1>
<p>Let’s try something else from the JS world: Gulp. Now, the tool is not terrible, but it still requires a lot of dependencies.</p>
<p>The pipeline that was required for this task sounds very simple:</p>
<blockquote>
<p>bootstrap-kw{,-dark}.sass → Sass compiler → Autoprefixer → bootstrap{,-dark}.css → minify → bootstrap{,-dark}.min.css</p>
</blockquote>
<p>The Gulp version is fairly simple: (I based it on examples on Gulp’s website, and pages of all my dependencies).</p>
<div class="code"><pre class="code javascript"><a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-1" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-1"></a><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">src</span><span class="p">,</span><span class="w"> </span><span class="nx">dest</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;gulp&#39;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-2" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">minifyCSS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;gulp-csso&#39;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-3" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-3"></a><span class="kd">const</span><span class="w"> </span><span class="nx">sass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;gulp-sass&#39;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-4" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-4"></a><span class="kd">const</span><span class="w"> </span><span class="nx">postcss</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;gulp-postcss&#39;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-5" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-5"></a><span class="kd">const</span><span class="w"> </span><span class="nx">autoprefixer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;autoprefixer&#39;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-6" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-6"></a><span class="kd">const</span><span class="w"> </span><span class="nx">rename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s2">&quot;gulp-rename&quot;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-7" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-7" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-7"></a><span class="nx">sass</span><span class="p">.</span><span class="nx">compiler</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;node-sass&#39;</span><span class="p">);</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-8" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-8" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-8"></a>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-9" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-9" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-9"></a>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-10" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-10" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-10"></a><span class="kd">function</span><span class="w"> </span><span class="nx">css</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-11" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-11" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-11"></a><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">src</span><span class="p">(</span><span class="s1">&#39;*.scss&#39;</span><span class="p">)</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-12" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-12" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-12"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">sass</span><span class="p">.</span><span class="nx">sync</span><span class="p">().</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">sass</span><span class="p">.</span><span class="nx">logError</span><span class="p">))</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-13" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-13" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-13"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">postcss</span><span class="p">([</span><span class="nx">autoprefixer</span><span class="p">()]))</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-14" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-14" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-14"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">rename</span><span class="p">(</span><span class="kd">function</span><span class="w"> </span><span class="p">(</span><span class="nx">path</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-15" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-15" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-15"></a><span class="w">            </span><span class="nx">path</span><span class="p">.</span><span class="nx">basename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">basename</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s2">&quot;-kw&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-16" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-16" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-16"></a><span class="w">        </span><span class="p">}))</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-17" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-17" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-17"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">dest</span><span class="p">(</span><span class="s1">&#39;assets/css&#39;</span><span class="p">))</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-18" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-18" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-18"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">minifyCSS</span><span class="p">())</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-19" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-19" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-19"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">rename</span><span class="p">(</span><span class="kd">function</span><span class="w"> </span><span class="p">(</span><span class="nx">path</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-20" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-20" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-20"></a><span class="w">            </span><span class="nx">path</span><span class="p">.</span><span class="nx">basename</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">&quot;.min&quot;</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-21" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-21" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-21"></a><span class="w">        </span><span class="p">}))</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-22" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-22" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-22"></a><span class="w">        </span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">dest</span><span class="p">(</span><span class="s1">&#39;assets/css&#39;</span><span class="p">));</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-23" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-23" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-23"></a><span class="p">}</span>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-24" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-24" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-24"></a>
<a id="rest_code_8d37d82eea6a4d9c81ae9635855db77c-25" name="rest_code_8d37d82eea6a4d9c81ae9635855db77c-25" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_8d37d82eea6a4d9c81ae9635855db77c-25"></a><span class="nx">exports</span><span class="p">.</span><span class="k">default</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">css</span><span class="p">;</span>
</pre></div>
</section>
<section id="a-node-modules-extravaganza">
<h1>A node_modules extravaganza</h1>
<p>Can you see all the <code class="docutils literal">require</code> lines at the top? Every one of them is a dependency of my build script. With the exception of <code class="docutils literal"><span class="pre">gulp-rename</span></code>, which IMO should be a built-in part of Gulp (it’s 45 lines of code and no external dependencies), the list is sensible.</p>
<p>Well, I already mentioned the size of <code class="docutils literal">node_modules</code>: 51 MiB according to <code class="docutils literal">du</code> (size-on-disk measurement). How many packages are there?</p>
<blockquote>
<ol class="arabic simple" start="545">
<li><p>Five hundred and forty-five packages.</p></li>
</ol>
</blockquote>
<p>Whoa, when did that happen? Most of it comes from gulp/gulp-cli (384 packages), with node-sass taking the second place (177 packages). Some of those are shared between libraries, and a few more belong to the other requirements. And many of these dependencies are a disgrace to programming.</p>
<p>After a full install of my <code class="docutils literal">package.json</code>, npm says <code class="docutils literal">added 545 packages from 331 contributors and audited 10500 packages in 22.458s</code>.  I’ve implicitly agreed to licenses imposed by 331 random people. All to build some simple CSS files out of SASS.</p>
<p>Let’s go on a tour of <code class="docutils literal">node_modules</code> and see what we ended up with.</p>
<section id="polyfills-reimplementations-oh-my">
<h2>Polyfills, reimplementations, oh my!</h2>
<p>Everything I’ve installed is meant to be used on top of Node.js. Node runs on top of the V8 engine, coming from Chrome. They’ve had almost-full ES2015 (ES6) support since April 2016. And yet, my node_modules is full of small polyfills.</p>
<p>Let’s pick a random one and work back from it: <code class="docutils literal"><span class="pre">number-is-nan</span></code>.</p>
<div class="code"><pre class="code javascript"><a id="rest_code_a2683c20074f46bb831a6d28854b02f1-1" name="rest_code_a2683c20074f46bb831a6d28854b02f1-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_a2683c20074f46bb831a6d28854b02f1-1"></a><span class="c1">// Copyright © Sindre Sorhus, MIT license</span>
<a id="rest_code_a2683c20074f46bb831a6d28854b02f1-2" name="rest_code_a2683c20074f46bb831a6d28854b02f1-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_a2683c20074f46bb831a6d28854b02f1-2"></a><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Number</span><span class="p">.</span><span class="nb">isNaN</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_a2683c20074f46bb831a6d28854b02f1-3" name="rest_code_a2683c20074f46bb831a6d28854b02f1-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_a2683c20074f46bb831a6d28854b02f1-3"></a><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">x</span><span class="w"> </span><span class="o">!==</span><span class="w"> </span><span class="nx">x</span><span class="p">;</span>
<a id="rest_code_a2683c20074f46bb831a6d28854b02f1-4" name="rest_code_a2683c20074f46bb831a6d28854b02f1-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_a2683c20074f46bb831a6d28854b02f1-4"></a><span class="p">};</span>
</pre></div>
<p>That’s a one-liner that re-implements <code class="docutils literal">Number.isNaN</code> if it’s not available, which is, according to MDN, <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN">a more robust version of the original, global
isNaN()</a>. The original function coerced everything to Number before testing, which apparently wasn’t enough. That’s what you get for using a language designed in a week. The function was added to all sane browsers and Node around 2013, the polyfill was created in 2015.</p>
<p>It was pulled in by <code class="docutils literal"><span class="pre">is-fullwidth-code-point</span></code> and <code class="docutils literal"><span class="pre">is-finite</span></code>, both by the same author. The latter one is especially interesting: it’s at version 1.0.2. Version 1.0.1improved the codebase from <code class="docutils literal">if (x) { return false; } return true</code> (via pull request), and version 1.0.2 replaced a manual <code class="docutils literal">val !== val</code> comparison with <code class="docutils literal"><span class="pre">number-is-nan</span></code>. <code class="docutils literal"><span class="pre">number-is-nan</span></code> has 7.5 million weekly downloads, <code class="docutils literal"><span class="pre">is-finite</span></code> has 6.7M. The build of <code class="docutils literal"><span class="pre">number-is-nan</span></code> <a class="reference external" href="https://travis-ci.org/sindresorhus/number-is-nan/builds/363709421">is currently failing.</a></p>
</section>
<section id="fifty-shades-of-terminal">
<h2>Fifty shades of terminal</h2>
<p>Every Node-based CLI tool wants to be cool. And for that, they need colors.</p>
<p>How does this work in Bash? You could use <code class="docutils literal">tput setaf XX</code>, but many people would just manually <code class="docutils literal">echo '\033[XXm'</code>, the codes are available <a class="reference external" href="https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit">in Wikipedia</a> or elsewhere.</p>
<p>How does this work in Python? There are a few libraries for this (and you can always do it manually), but the most popular one is <a class="reference external" href="https://pypi.org/project/colorama/">colorama</a>. That library can even handle Windows.</p>
<p>What is available in Node?</p>
<ul class="simple">
<li><p><code class="docutils literal"><span class="pre">color-support</span></code> and <code class="docutils literal"><span class="pre">supports-color</span></code> are both part of my <code class="docutils literal">node_modules</code>.</p></li>
<li><p>There seems to be a fairly advanced <code class="docutils literal">chalk</code> library, by the aforementioned Sindre Sorhus.</p></li>
<li><p><code class="docutils literal"><span class="pre">ansi-colors</span></code> seems to be another, smaller option for it, it claims to be 10-20x faster than <code class="docutils literal">chalk</code>.</p></li>
<li><p>There’s a package called <code class="docutils literal"><span class="pre">has-ansi</span></code> which checks if a string has ANSI escapes in it. It depends on <code class="docutils literal"><span class="pre">ansi-regex</span></code>.</p></li>
<li><p>Also, <code class="docutils literal"><span class="pre">strip-ansi</span></code> also uses <code class="docutils literal"><span class="pre">ansi-regex</span></code>. All three packages are basically one liners. One exports a regex, the other two do replacement/search with it.</p></li>
<li><p>There’s <code class="docutils literal"><span class="pre">wrap-ansi</span></code> and <code class="docutils literal"><span class="pre">ansi-wrap</span></code>. <code class="docutils literal"><span class="pre">wrap-ansi</span></code> intelligently wraps a string with ANSI escapes in it.  <code class="docutils literal"><span class="pre">ansi-wrap</span></code> takes three strings and  returns <code class="docutils literal"><span class="pre">'\u001b['+</span> a + 'm' + msg + '\u001b[' + b + 'm'</code> (Copyright © Jon Schlinkert, MIT license)</p></li>
<li><p>There’s also <code class="docutils literal"><span class="pre">ansi-gray</span></code>, which calls <code class="docutils literal"><span class="pre">ansi-wrap</span></code> with a = 90, b = 39, and a user-specified message. (Copyright © Jon Schlinkert, MIT license)</p></li>
<li><p><code class="docutils literal"><span class="pre">ansi-red</span></code> and <code class="docutils literal"><span class="pre">ansi-cyan</span></code> are very similar libraries to <code class="docutils literal"><span class="pre">ansi-gray</span></code>. Is this a joke?!</p></li>
</ul>
<p>There are definitely other <code class="docutils literal"><span class="pre">ansi-$color</span></code> libraries, although they are not in my <code class="docutils literal">node_modules</code>. And probably other libraries for color support, but either they are not installed, or I haven’t managed to spot them in my <code class="docutils literal">npm list</code> output.</p>
</section>
<section id="copyrighted-one-liners">
<h2>Copyrighted one-liners</h2>
<p>Another famous library by Jon Schlinkert is called <code class="docutils literal"><span class="pre">is-even</span></code>. Here is the complete code, verbatim:</p>
<div class="code"><pre class="code javascript"><a id="rest_code_378011c1be194c9890b6fd516663f9b0-1" name="rest_code_378011c1be194c9890b6fd516663f9b0-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-1"></a><span class="cm">/*!</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-2" name="rest_code_378011c1be194c9890b6fd516663f9b0-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-2"></a><span class="cm"> * is-even &lt;https://github.com/jonschlinkert/is-even&gt;</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-3" name="rest_code_378011c1be194c9890b6fd516663f9b0-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-3"></a><span class="cm"> *</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-4" name="rest_code_378011c1be194c9890b6fd516663f9b0-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-4"></a><span class="cm"> * Copyright (c) 2015, 2017, Jon Schlinkert.</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-5" name="rest_code_378011c1be194c9890b6fd516663f9b0-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-5"></a><span class="cm"> * Released under the MIT License.</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-6" name="rest_code_378011c1be194c9890b6fd516663f9b0-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-6"></a><span class="cm"> */</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-7" name="rest_code_378011c1be194c9890b6fd516663f9b0-7" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-7"></a>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-8" name="rest_code_378011c1be194c9890b6fd516663f9b0-8" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-8"></a><span class="s1">&#39;use strict&#39;</span><span class="p">;</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-9" name="rest_code_378011c1be194c9890b6fd516663f9b0-9" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-9"></a>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-10" name="rest_code_378011c1be194c9890b6fd516663f9b0-10" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-10"></a><span class="kd">var</span><span class="w"> </span><span class="nx">isOdd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;is-odd&#39;</span><span class="p">);</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-11" name="rest_code_378011c1be194c9890b6fd516663f9b0-11" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-11"></a>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-12" name="rest_code_378011c1be194c9890b6fd516663f9b0-12" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-12"></a><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">isEven</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-13" name="rest_code_378011c1be194c9890b6fd516663f9b0-13" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-13"></a><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="o">!</span><span class="nx">isOdd</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<a id="rest_code_378011c1be194c9890b6fd516663f9b0-14" name="rest_code_378011c1be194c9890b6fd516663f9b0-14" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_378011c1be194c9890b6fd516663f9b0-14"></a><span class="p">};</span>
</pre></div>
<p><code class="docutils literal"><span class="pre">is-odd</span></code> is slightly longer:</p>
<div class="code"><pre class="code javascript"><a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-1" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-1"></a><span class="cm">/*!</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-2" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-2"></a><span class="cm"> * is-odd &lt;https://github.com/jonschlinkert/is-odd&gt;</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-3" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-3"></a><span class="cm"> *</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-4" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-4"></a><span class="cm"> * Copyright (c) 2015-2017, Jon Schlinkert.</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-5" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-5"></a><span class="cm"> * Released under the MIT License.</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-6" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-6"></a><span class="cm"> */</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-7" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-7" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-7"></a>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-8" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-8" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-8"></a><span class="s1">&#39;use strict&#39;</span><span class="p">;</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-9" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-9" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-9"></a>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-10" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-10" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-10"></a><span class="kd">const</span><span class="w"> </span><span class="nx">isNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;is-number&#39;</span><span class="p">);</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-11" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-11" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-11"></a>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-12" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-12" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-12"></a><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">isOdd</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-13" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-13" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-13"></a><span class="w">  </span><span class="kd">const</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-14" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-14" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-14"></a><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">isNumber</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-15" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-15" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-15"></a><span class="w">    </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">TypeError</span><span class="p">(</span><span class="s1">&#39;expected a number&#39;</span><span class="p">);</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-16" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-16" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-16"></a><span class="w">  </span><span class="p">}</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-17" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-17" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-17"></a><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nb">Number</span><span class="p">.</span><span class="nx">isInteger</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-18" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-18" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-18"></a><span class="w">    </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;expected an integer&#39;</span><span class="p">);</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-19" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-19" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-19"></a><span class="w">  </span><span class="p">}</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-20" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-20" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-20"></a><span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nb">Number</span><span class="p">.</span><span class="nx">isSafeInteger</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-21" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-21" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-21"></a><span class="w">    </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="s1">&#39;value exceeds maximum safe integer&#39;</span><span class="p">);</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-22" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-22" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-22"></a><span class="w">  </span><span class="p">}</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-23" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-23" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-23"></a><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mf">2</span><span class="p">)</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="mf">1</span><span class="p">;</span>
<a id="rest_code_ba74033fe5774b6a99b06073158aa6aa-24" name="rest_code_ba74033fe5774b6a99b06073158aa6aa-24" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ba74033fe5774b6a99b06073158aa6aa-24"></a><span class="p">};</span>
</pre></div>
<p><code class="docutils literal"><span class="pre">is-number</span></code> is another fun library; it says <code class="docutils literal">true</code> for strings of numbers, and <code class="docutils literal">false</code> for NaN (<code class="docutils literal">typeof NaN === 'number'</code>). <code class="docutils literal"><span class="pre">is-even</span></code> is used by, for example, <code class="docutils literal">even</code>, which calls <code class="docutils literal">Array.filter</code> with <code class="docutils literal"><span class="pre">is-even</span></code> as the argument. There’s also <code class="docutils literal">odd</code>, and for some reason, the two packages are separate.</p>
<p>The checks found in <code class="docutils literal"><span class="pre">is-odd</span></code> make some more sense if you’re working with a dynamically-typed language where every number is a float (like JS). But you could release <code class="docutils literal"><span class="pre">check-odd</span></code>, which is 100x faster than <code class="docutils literal"><span class="pre">is-odd</span></code> (it assumes its input is correct), and exports <code class="docutils literal">function checkOdd(value) { return (value % 2) !== 0; }</code> <s>(Copyright © 2019, Chris Warrick. Licensed under the 4-clause BSD license.)</s> <i>(No, not really.)</i></p>
</section>
<section id="this-product-includes-software-developed-by">
<h2>This product includes software developed by…</h2>
<p>Hold on a second, 4-clause BSD? That license contains the following clause:</p>
<div class="code"><pre class="code text"><a id="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-1" name="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-1"></a>Redistribution and use in source and binary forms, with or without
<a id="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-2" name="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-2"></a>modification, are permitted provided that the following conditions are met:
<a id="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-3" name="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-3"></a>3. All advertising materials mentioning features or use of this software
<a id="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-4" name="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-4"></a>   must display the following acknowledgement:
<a id="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-5" name="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-5"></a>     This product includes software developed by the University of
<a id="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-6" name="rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_ea3ba871c1584ff2a5763cf5f0d2f996-6"></a>     California, Berkeley and its contributors.
</pre></div>
<p>This clause was removed by UC Berkeley in 1999, but there is still old code that has clauses (with other names), and someone could create something with the old license. I actually found one such clause in my <code class="docutils literal">node_modules</code> (from <code class="docutils literal">bcrypt_pbkdf</code>). <a class="reference external" href="https://www.gnu.org/licenses/bsd.html">NetBSD had 75 different clauses</a> in 1997. It would be fun to see figures for the Node ecosystem… or more packages with equally problematic clauses.</p>
<p>Most people aren’t aware of the licenses of their node dependencies. Going back to Colorama, I can quickly verify that Colorama has no dependencies, and itself uses the 3-clause BSD license. (That version of the license lacks the advertising clause and is considered GPL-compatible.) There is a helpful <code class="docutils literal"><span class="pre">license-checker</span></code> package that can tell you what licenses you have (based on the details provided in <code class="docutils literal">package.json</code>)</p>
<div class="code"><pre class="code text"><a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-1" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-1"></a>├─ MIT: 380
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-2" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-2"></a>├─ ISC: 64
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-3" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-3"></a>├─ Apache-2.0: 10
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-4" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-4"></a>├─ BSD-3-Clause: 10
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-5" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-5"></a>├─ BSD-2-Clause: 3
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-6" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-6"></a>├─ CC-BY-3.0: 2
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-7" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-7" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-7"></a>├─ BSD-3-Clause OR MIT: 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-8" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-8" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-8"></a>├─ MIT*: 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-9" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-9" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-9"></a>├─ (MIT OR Apache-2.0): 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-10" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-10" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-10"></a>├─ CC-BY-4.0: 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-11" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-11" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-11"></a>├─ AFLv2.1,BSD: 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-12" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-12" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-12"></a>├─ MPL-2.0: 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-13" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-13" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-13"></a>├─ (BSD-2-Clause OR MIT OR Apache-2.0): 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-14" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-14" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-14"></a>├─ CC0-1.0: 1
<a id="rest_code_dc998613f10743b79f3dcc648a0ef63b-15" name="rest_code_dc998613f10743b79f3dcc648a0ef63b-15" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_dc998613f10743b79f3dcc648a0ef63b-15"></a>└─ Unlicense: 1
</pre></div>
</section>
</section>
<section id="attempt-2-back-to-bash">
<h1>Attempt 2: back to Bash</h1>
<p>I decided to get rid of Gulp, it’s not necessary for this pipeline. I replaced
it with Bash and <code class="docutils literal"><span class="pre">postcss-cli</span></code>. <code class="docutils literal"><span class="pre">node-sass</span></code> was replaced by <code class="docutils literal"><span class="pre">dart-sass</span></code>
(a two-file binary distribution), and <code class="docutils literal">csso</code> was replaced by <code class="docutils literal">cssnano</code> (it
works with postcss). Here is the resulting Bash file:</p>
<div class="code"><pre class="code bash"><a id="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-1" name="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-1"></a>sass<span class="w"> </span>bootstrap-kw.scss<span class="w"> </span><span class="p">|</span><span class="w"> </span>npx<span class="w"> </span>postcss<span class="w"> </span>--no-map<span class="w"> </span>--use<span class="w"> </span>autoprefixer<span class="w"> </span>-o<span class="w"> </span>assets/css/bootstrap.css
<a id="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-2" name="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-2"></a>sass<span class="w"> </span>bootstrap-kw-dark.scss<span class="w"> </span><span class="p">|</span><span class="w"> </span>npx<span class="w"> </span>postcss<span class="w"> </span>--no-map<span class="w"> </span>--use<span class="w"> </span>autoprefixer<span class="w"> </span>-o<span class="w"> </span>assets/css/bootstrap-dark.css
<a id="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-3" name="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-3"></a>npx<span class="w"> </span>postcss<span class="w"> </span>--no-map<span class="w"> </span>--use<span class="w"> </span>cssnano<span class="w"> </span>-o<span class="w"> </span>assets/css/bootstrap.min.css<span class="w"> </span>assets/css/bootstrap.css
<a id="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-4" name="rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_e7da6ca3345d49c48ed269ba2c43c0f1-4"></a>npx<span class="w"> </span>postcss<span class="w"> </span>--no-map<span class="w"> </span>--use<span class="w"> </span>cssnano<span class="w"> </span>-o<span class="w"> </span>assets/css/bootstrap-dark.min.css<span class="w"> </span>assets/css/bootstrap-dark.css
</pre></div>
<p>The simplified dependency list cost me 37 MiB of disk space, and I’ve got 438
packages from 232 contributors.</p>
</section>
<section id="attempt-3-node-clis-are-unnecessary">
<h1>Attempt 3: node CLIs are unnecessary</h1>
<p>Let’s try something else: replace <code class="docutils literal">npx postcss</code> with a custom tool.</p>
<div class="code"><pre class="code javascript"><a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-1" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-1"></a><span class="kd">const</span><span class="w"> </span><span class="nx">fs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;fs&#39;</span><span class="p">);</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-2" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-2"></a><span class="kd">const</span><span class="w"> </span><span class="nx">getStdin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;get-stdin&#39;</span><span class="p">);</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-3" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-3"></a>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-4" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-4"></a><span class="kd">const</span><span class="w"> </span><span class="nx">postcss</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;postcss&#39;</span><span class="p">);</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-5" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-5"></a><span class="kd">const</span><span class="w"> </span><span class="nx">autoprefixer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;autoprefixer&#39;</span><span class="p">);</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-6" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-6" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-6"></a><span class="kd">const</span><span class="w"> </span><span class="nx">cssnano</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">&#39;cssnano&#39;</span><span class="p">);</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-7" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-7" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-7"></a>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-8" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-8" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-8"></a><span class="kd">const</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">[</span><span class="mf">2</span><span class="p">];</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-9" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-9" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-9"></a>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-10" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-10" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-10"></a><span class="nx">getStdin</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">css</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-11" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-11" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-11"></a><span class="w">    </span><span class="nx">postcss</span><span class="p">([</span><span class="nx">autoprefixer</span><span class="p">]).</span><span class="nx">process</span><span class="p">(</span><span class="nx">css</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="kr">from</span><span class="o">:</span><span class="w"> </span><span class="kc">undefined</span><span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">result1</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-12" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-12" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-12"></a><span class="w">        </span><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="sb">`assets/css/</span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">.css`</span><span class="p">,</span><span class="w"> </span><span class="nx">result1</span><span class="p">.</span><span class="nx">css</span><span class="p">);</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-13" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-13" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-13"></a>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-14" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-14" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-14"></a><span class="w">        </span><span class="nx">postcss</span><span class="p">([</span><span class="nx">cssnano</span><span class="p">]).</span><span class="nx">process</span><span class="p">(</span><span class="nx">result1</span><span class="p">.</span><span class="nx">css</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="kr">from</span><span class="o">:</span><span class="w"> </span><span class="kc">undefined</span><span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">result2</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-15" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-15" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-15"></a><span class="w">            </span><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="sb">`assets/css/</span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">.min.css`</span><span class="p">,</span><span class="w"> </span><span class="nx">result2</span><span class="p">.</span><span class="nx">css</span><span class="p">)</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-16" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-16" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-16"></a><span class="w">        </span><span class="p">});</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-17" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-17" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-17"></a><span class="w">    </span><span class="p">});</span>
<a id="rest_code_fde41d35cf7f4c38b971971c3de749b3-18" name="rest_code_fde41d35cf7f4c38b971971c3de749b3-18" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_fde41d35cf7f4c38b971971c3de749b3-18"></a><span class="p">});</span>
</pre></div>
<p>The bash script now pipes <code class="docutils literal">sass</code> output to <code class="docutils literal">node run_postcss.js <span class="pre">bootstrap(-dark)</span></code>.</p>
<p>Doing this… cost me a new dependency. Its name is <code class="docutils literal"><span class="pre">get-stdin</span></code>. We’ve already met its author, Sindre Sorhus. While the library has its deficiencies <a class="reference external" href="https://github.com/sindresorhus/get-stdin/issues/21">(namely, it doesn’t support reading from TTY)</a>, it’s good enough. I could do it manually or use some other tricks, but since <code class="docutils literal"><span class="pre">get-stdin</span></code> does not pull in any other dependencies, I’m going to accept it. After cleaning up <code class="docutils literal">packages.json</code>, we end up with:</p>
<div class="code"><pre class="code console"><a id="rest_code_d17060d8dc0849a98c962270d526c071-1" name="rest_code_d17060d8dc0849a98c962270d526c071-1" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_d17060d8dc0849a98c962270d526c071-1"></a><span class="gp">$ </span>npm<span class="w"> </span>install
<a id="rest_code_d17060d8dc0849a98c962270d526c071-2" name="rest_code_d17060d8dc0849a98c962270d526c071-2" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_d17060d8dc0849a98c962270d526c071-2"></a><span class="go">added 144 packages from 119 contributors and audited 637 packages in 8.127s</span>
<a id="rest_code_d17060d8dc0849a98c962270d526c071-3" name="rest_code_d17060d8dc0849a98c962270d526c071-3" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_d17060d8dc0849a98c962270d526c071-3"></a><span class="go">found 0 vulnerabilities</span>
<a id="rest_code_d17060d8dc0849a98c962270d526c071-4" name="rest_code_d17060d8dc0849a98c962270d526c071-4" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_d17060d8dc0849a98c962270d526c071-4"></a><span class="gp">$ </span>du<span class="w"> </span>-hs<span class="w"> </span>node_modules
<a id="rest_code_d17060d8dc0849a98c962270d526c071-5" name="rest_code_d17060d8dc0849a98c962270d526c071-5" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/#rest_code_d17060d8dc0849a98c962270d526c071-5"></a><span class="go"> 21M    node_modules</span>
</pre></div>
</section>
<section id="conclusion">
<h1>Conclusion</h1>
<p>The task at hand was very simple. So was the JS code (Gulp and custom) I had to write to implement it. But underneath, there was a mess of unknown, unaudited code, duplicated libraries, and libraries created effectively to bump people’s npm download stats. There were already incidents, like <code class="docutils literal"><span class="pre">left-pad</span></code> (the removal of which broke Babel), or <code class="docutils literal"><span class="pre">event-stream</span></code> (which was taken over and modified to steal cryptocurrencies). The modern web development ecosystem is a huge mess of dependencies and one-line packages. Some of them are necessary due to the lackluster JS standard library — but some are just useless. And some of these micro-packages would be better off as larger libraries.</p>
<blockquote>
<p>Sure, the package count went down from the original 545 to 144. But the original point still stands: too much useless stuff.</p>
</blockquote>
<p>PS. Five of the packages (in the “large” set) had a <code class="docutils literal">.DS_Store</code> file left over. I’m wondering if there are any other files that shouldn’t be shipped with packages, lurking in <code class="docutils literal">node_modules</code> directories all over the world…</p>
<p>PPS. I’ve replaced Disqus with Isso, because it had too many advertisements. If you experience any issues with the comment system (after force-refreshing), e-mail me.</p>
</section>
]]></content:encoded><category>Internet</category><category>JavaScript</category><category>rant</category><category>web development</category></item><item><title>Static Site Generator Speed Test (Nikola, Pelican, Hexo, Octopress)</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2015/07/23/ssg-speed-test/</link><pubDate>Thu, 23 Jul 2015 15:10:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2015/07/23/ssg-speed-test/</guid><description>
I tested the speed of four static site generators: Nikola, Pelican, Hexo and Octopress, in a clean environment.  Spoiler alert: Nikola won.
Disclaimer: author is a developer and user of Nikola.  The test environments used were the same for all four generators.

Generators tested

Nikola v7.6.1, by Roberto Alsina, Chris Warrick and contributors; Python; MIT license
Pelican v3.6.0, by Alexis Metaireau and contributors; Python; GNU AGPL license
Hexo v3.1.1, by Tommy Chen and contributors; Node.js; MIT license
Octopress v2.0, by Brandon Mathis and contributors; Ruby; MIT license (based on Jekyll)

</description><content:encoded><![CDATA[
<p>I tested the speed of four static site generators: Nikola, Pelican, Hexo and Octopress, in a clean environment.  Spoiler alert: Nikola won.</p>
<p><em>Disclaimer:</em> author is a developer and user of Nikola.  The test environments used were the same for all four generators.</p>
<section id="generators-tested">
<h1>Generators tested</h1>
<ul class="simple">
<li><p><a class="reference external" href="https://getnikola.com/">Nikola</a> v7.6.1, by Roberto Alsina, Chris Warrick and contributors; Python; MIT license</p></li>
<li><p><a class="reference external" href="http://blog.getpelican.com/">Pelican</a> v3.6.0, by Alexis Metaireau and contributors; Python; GNU AGPL license</p></li>
<li><p><a class="reference external" href="https://hexo.io/">Hexo</a> v3.1.1, by Tommy Chen and contributors; Node.js; MIT license</p></li>
<li><p><a class="reference external" href="http://octopress.org/">Octopress</a> v2.0, by Brandon Mathis and contributors; Ruby; MIT license (based on Jekyll)</p></li>
</ul>



</section>
<section id="setup">
<h1>Setup</h1>
<p>Every site generator was set up in an identical <strong>clean</strong> environment, using Ubuntu 15.04, x64, as a 512 MB DigitalOcean VM with a 20 GB SSD drive. The machine was updated, an user account with passwordless sudo was created, and <code class="docutils literal"><span class="pre">build-essential</span></code> was installed. Tests were run by an automated installer and timer, written in Bash and C, respectively (custom; source code is available). Pre-compiled wheels for lxml and Pillow were used for Nikola testing, because lxml cannot be compiled with less than 1.5 GB of RAM; they were built with <code class="docutils literal">pip wheel lxml pillow</code> outside of the testing environment (on a local VM). The machine was reimaged after every test. Lists of installed Python/Ruby/Node packages are available in the GitHub repo (see below).</p>
</section>
<section id="input">
<h1>Input</h1>
<p>Every site generator was given the same set of 179 log files from #nikola on freenode. The raw logs contain 1209507 bytes (1.1 MiB) of plain text. The logs were processed into post files, which fit the format of each engine (reST or Markdown), containing mandatory metadata, an introductory paragraph and a code block (using <code class="docutils literal">::</code> for reST, four spaces for Markdown). One file had to be altered, because they contained the <code class="docutils literal">{{</code>  sequence, which was misinterpreted as internal templating by Hexo and Octopress — it was replaced by a harmless <code class="docutils literal">~~</code> sequence for all four generators.</p>
<p>The generators used default config, with one exception: highlighting was disabled for Hexo. The highlighting would cause an unfair advantage (other generators did not automatically highlight the code boxes), and led to very high build times (see table 4 in comparison spreadsheet).</p>
</section>
<section id="build">
<h1>Build</h1>
<p>Sites were built a total of 110 times, in 10 cycles of 11 builds each. The first build of a cycle was a fresh build, the remaining 10 were rebuilds. Sites and cache files were removed after each cycle.</p>
</section>
<section id="results">
<h1>Results</h1>
<p>Because Nikola and Hexo use incremental rebuilds, the results were compared in two groups: 11 and 10 runs.</p>
<section id="average-build-times-in-seconds">
<h2>Average build times (in seconds)</h2>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>Generator</th>
<th>Average of 11 runs</th>
<th>Average of 10 runs</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>Nikola</td>
<td>2.38290</td>
<td>2.06057</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Pelican</td>
<td>2.61924</td>
<td>2.62352</td>
</tr>
<tr>
<th scope="row">3</th>
<td>Hexo</td>
<td>6.27361</td>
<td>6.21267</td>
</tr>
<tr>
<th scope="row">4</th>
<td>Octopress</td>
<td>9.57618</td>
<td>9.47550</td>
</tr>
</tbody>
</table></section>
<section id="full-results">
<h2>Full results</h2>
<p class="lead">Full results are available in <a class="reference external" href="https://chriswarrick.com/pub/ssg-test-results.ods">ods format</a>.</p>
</section>
<section id="raw-results-and-configuration">
<h2>Raw results and configuration</h2>
<p>Raw results (<code class="docutils literal">.csv</code> files from the test runner) and configuration is available in the <a class="reference external" href="https://github.com/Kwpolska/ssg-test">GitHub repo</a>. Log files and converted posts are not available publicly; however, they can be provided to interested parties (<a class="reference external" href="https://chriswarrick.com/contact/">contact me</a> to obtain them).</p>
</section>
</section>
<section id="questions-and-answers">
<h1>Questions and answers</h1>
<section id="why-not-plain-jekyll">
<h2>Why not plain Jekyll?</h2>
<p><strong>Plain Jekyll was disqualified</strong> on the basis of missing many features other generators have, leading to an unfair advantage. The aim of this test was to provide similar setups for each of the four generators. Jekyll generates a very basic site that lacks some elements; a Jekyll site does not have paginated indexes, (partial) post text on indexes, any sort of archives, etc. A Jekyll site contains only one CSS file, index.html, feed.xml, and the log posts. On the other hand, sites generated by Pelican, Nikola and Hexo contain more files, which makes the builds longer and the website experience richer (archives, JS, sitemaps, tag listings).</p>
<p>On the basis of the above, <strong>Octopress</strong> was chosen to represent the Jekyll universe at large. Octopress sites have more assets, a sitemap, archives and category listings — making it comparable to the other four contenders. However, tests were performed for Jekyll. The average result from 11 builds was 2.22118, while the average result from 10 builds was 2.23903. The result would land Jekyll on the 1st place for 11 builds, and on the 2nd place for 10 builds.</p>
</section>
<section id="why-not-myfavoritessg">
<h2>Why not $MYFAVORITESSG?</h2>
<p>I tested only four popular generators that were easy enough to set up. I could easily extend the set if I had time and friendly enough documentation to do so. I can add a SSG, provided that:</p>
<ul class="simple">
<li><p>it’s easy to configure</p></li>
<li><p>it has a default config that provides a working site with a feature set comparable to other SSGs tested here (see <a class="reference internal" href="https://chriswarrick.com/blog/2015/07/23/ssg-speed-test/#why-not-plain-jekyll">Why not plain Jekyll?</a>)</p></li>
</ul>
</section>
</section>
]]></content:encoded><category>Internet</category><category>blog</category><category>Hexo</category><category>jekyll</category><category>Nikola</category><category>Octopress</category><category>Pelican</category><category>Python</category><category>static site generators</category><category>test</category><category>web development</category></item></channel></rss>