<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[console.blog]]></title><description><![CDATA[Messages about web development, software, technology, and more]]></description><link>https://blog.andrewbrey.com</link><generator>GatsbyJS</generator><lastBuildDate>Wed, 19 Jun 2024 23:16:27 GMT</lastBuildDate><atom:link href="https://blog.andrewbrey.com/rss.xml" rel="self" type="application/rss+xml"/><author><![CDATA[Andrew Brey]]></author><language><![CDATA[en]]></language><ttl>10080</ttl><item><title><![CDATA[Your Monorepo Dependencies Are Asking for Trouble]]></title><description><![CDATA[An exploration of a common dependency version issue in monorepo projects, and the method I use to solve the issue in my own projects]]></description><link>https://blog.andrewbrey.com/2022-10-12-your-monorepo-dependencies-are-asking-for-trouble/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2022-10-12-your-monorepo-dependencies-are-asking-for-trouble/</guid><category><![CDATA[Exploration]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Wed, 12 Oct 2022 18:55:15 GMT</pubDate><content:encoded>&lt;p&gt;When I wrote the title of this post, I had planned on saying “please forgive me for using such a click-bait title” but
I’ve decided that, no, I actually do kinda think your (or, well the Royal “Your”) dependency management &lt;em&gt;just might&lt;/em&gt; be
asking for trouble.&lt;/p&gt;
&lt;p&gt;The topic I want to discuss in this post, monorepo workspaces with incompatible dependency versions, definitely doesn’t
apply to every monorepo, and also, even if it &lt;em&gt;does&lt;/em&gt; apply to &lt;em&gt;your&lt;/em&gt; monorepo, there’s more than one way to solve for
it.&lt;/p&gt;
&lt;p&gt;This post will talk about the method I use in my own projects to ensure my monorepo workspaces don’t use conflicting
dependency versions. &lt;em&gt;Any&lt;/em&gt; choice of how to handle this topic in your own monorepo will come with tradeoffs, and my
method is no exception. We’ll talk about those tradeoffs at the end 👍.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; — this topic isn’t &lt;em&gt;exclusive&lt;/em&gt; to the JavaScript ecosystem, but it is a topic I’ve encountered as I work to
build &lt;a href=&quot;https://lemmyapp.com&quot;&gt;the Lemmy App&lt;/a&gt;, the code for which lives in a TypeScript (mostly) monorepo, so discussion
and examples in this post will be from the perspective of a &lt;code class=&quot;language-text&quot;&gt;Node&lt;/code&gt; monorepo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;You’re building an application in a monorepo so that you can share as much code as possible between different concerns
of your project. Let’s say you’ve got a &lt;code class=&quot;language-text&quot;&gt;marketing-website&lt;/code&gt; workspace and a &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt; workspace and you want them both to
use the same UI components (buttons, accordions, typography, etc), so you decide you’ll make a shared &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; workspace
that both other workspaces can depend on.&lt;/p&gt;
&lt;p&gt;For simplicity, we’ll assume that these three workspaces all use &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; so there’s nothing complicated about getting
the &lt;code class=&quot;language-text&quot;&gt;marketing-website&lt;/code&gt; to render out the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;Button /&amp;gt;&lt;/code&gt; component from the shared &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt;, and similarly there’s nothing
complicated about getting the &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt; to render out the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;Sidebar /&amp;gt;&lt;/code&gt; component from the shared &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt;…right?&lt;/p&gt;
&lt;p&gt;Early in the project, yeah, that’s probably true! You’ll be able to just &lt;code class=&quot;language-text&quot;&gt;import { Button } from &amp;#39;ui-lib&amp;#39;;&lt;/code&gt; in either of
the other workspaces and you’ll see a button render.&lt;/p&gt;
&lt;p&gt;The problems lurk a little down the road when you want to bump the version of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; that your &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; uses to take
advantage of the awesome new features available in the latest version of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt;. The components in &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; &lt;em&gt;don’t
exist in a vacuum&lt;/em&gt; — they are only relevant in the context of the applications that import them, and what happens when
the applications that import them, in our example &lt;code class=&quot;language-text&quot;&gt;marketing-website&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt;, are on a version of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; that is
incompatible with the newest &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; features?&lt;/p&gt;
&lt;details class=&quot;px-3 py-1 bg-smoke-100 dark:bg-smoke-800 rounded select-none&quot;&gt;
	&lt;summary class=&quot;font-bold&quot;&gt;Spoiler Alert!!! Only click here if you want spoilers!!1!1&lt;/summary&gt;
	&lt;p&gt;💣 (It blows up at runtime...Sad App 😢)&lt;/p&gt;
&lt;/details&gt;
&lt;h3&gt;“Sad App” Demonstration&lt;/h3&gt;
&lt;div class=&quot;w-full hidden md:block&quot;&gt;
	&lt;iframe
		loading=&quot;lazy&quot;
		class=&quot;w-full&quot;
		style=&quot;aspect-ratio: 16/9;&quot;
		src=&quot;https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-sad-app?embed=1&amp;file=README.md&amp;hideNavigation=1&quot;&gt;
	&lt;/iframe&gt;
	&lt;p class=&quot;px-4 py-2 text-sm lg:text-base leading-snug bg-white text-gray-600 border border-gray-300 rounded-b-md dark:bg-fluencyy-900 dark:text-fluencyy-100 dark:border-fluencyy-700&quot;&gt;
		&lt;b&gt;FYI&lt;/b&gt; — as of this writing, StackBlitz doesn&apos;t (can&apos;t) &lt;i&gt;fully&lt;/i&gt; support embedded WebContainers in anything 
		except Chromium-based browsers. If you&apos;re in Firefox, Safari, or something else non-Chromium, you can still 
		read the project code right here but unfortunately the in-page preview won&apos;t run and you&apos;ll have to 
		&lt;a href=&quot;https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-sad-app&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;
			open the &quot;Sad App Monorepo&quot; demo directly
		&lt;/a&gt; to run it. Sorry &apos;bout that!
	&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;w-full md:hidden&quot;&gt;
	&lt;blockquote&gt;
		&lt;b&gt;Hey!&lt;/b&gt; — right here on larger screens there is an embedded demo using StackBlitz, but to be effective it needs a little more space than 
		your device provides. You&apos;ll be better served by opening the demo directly in another browser tab:
		&lt;a href=&quot;https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-sad-app&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;
			https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-sad-app
		&lt;/a&gt;
	&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p&gt;^ that demo is a bare-bones version of the exact scenario I talked about above (minus the “marketing website”), and what
we see is that when we upgraded the &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; so that we can use the super awesome new &lt;code class=&quot;language-text&quot;&gt;React.useId()&lt;/code&gt; API added in
&lt;code class=&quot;language-text&quot;&gt;react@18&lt;/code&gt; within the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;Button /&amp;gt;&lt;/code&gt; component, but don’t or &lt;em&gt;can’t&lt;/em&gt; upgrade the &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt; from where it’s at (&lt;code class=&quot;language-text&quot;&gt;react@16&lt;/code&gt;)
to also be on &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; version 18, we get a “crash” at runtime when the &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt; tries to render the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;Button /&amp;gt;&lt;/code&gt;…Sad
App 😢.&lt;/p&gt;
&lt;h2&gt;What Now&lt;/h2&gt;
&lt;p&gt;Of course, in this &lt;em&gt;exact&lt;/em&gt; scenario, the fix seems pretty simple and clear - just upgrade the &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt; to use
&lt;code class=&quot;language-text&quot;&gt;react@18&lt;/code&gt;, the project is tiny and it’ll take about 5 seconds to safely make the change. Also, of course, this &lt;em&gt;exact&lt;/em&gt;
scenario is not very realistic to the real world where you’ve got a lot more code of your own, a lot more code from 3rd
party libraries, and usually more people/teams involved in contributing to the project. Saying “just upgrade lol” is
almost rudely dismissive of the complexity that’s potentially involved in mitigating this pain point.&lt;/p&gt;
&lt;p&gt;So what should we do? There’s lots of things we might want to look into that can help fix our broken &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt;. We could
have the &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; declare &lt;code class=&quot;language-text&quot;&gt;react@^18&lt;/code&gt; as a &lt;code class=&quot;language-text&quot;&gt;peerDependency&lt;/code&gt; and that would at least potentially help us realize at
author-time that we’re careening towards a pitfall…that would be helpful, but it doesn’t actually solve the issue, and
in a lot of ways it kind of makes it worse because now we’ve got &lt;em&gt;another classification of dependency&lt;/em&gt; we need to worry
about managing. You don’t have to look hard on Stack Overflow to find threads filled with people frustrated and confused
by peer dependencies.&lt;/p&gt;
&lt;p&gt;Maybe there’s something we could do with &lt;code class=&quot;language-text&quot;&gt;package resolutions&lt;/code&gt; if our package manager supports them (&lt;code class=&quot;language-text&quot;&gt;yarn&lt;/code&gt; does, which
is what I use)? I don’t really see how that would help in this scenario, but it’s the kind of thing people toss out
dismissively when you try and seek help on the internet for this type of issue.&lt;/p&gt;
&lt;p&gt;Maybe we should just switch all of the projects to &lt;code class=&quot;language-text&quot;&gt;svelte&lt;/code&gt; so that we’re shipping compiled-to-vanilla JavaScript and
all of our problems are solved??? (just jokin’ &lt;code class=&quot;language-text&quot;&gt;svelte&lt;/code&gt; is super neat, and I’m eager to build something real with it,
but it’s also really common for people to suggest “just use a different tool” as the solution to a problem like this,
and it annoys me)&lt;/p&gt;
&lt;p&gt;I contend that there’s really only two options available that &lt;em&gt;actually&lt;/em&gt; solve for &lt;em&gt;this&lt;/em&gt; pain point:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;don’t upgrade the &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; to use a newer version of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; (and by extension, don’t get access to new features, bug
fixes, security patches, etc)&lt;/li&gt;
&lt;li&gt;upgrade everything that uses &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; to use the newer version (and by extension, mandate upgrades to &lt;em&gt;everything&lt;/em&gt;
else that’s downstream and impacted by your choice of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; version)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;as you might agree, both of these options suck. In my opinion, option 1 is like the tautological choice where “not
making a choice is a choice”, and while technically a possibility especially for a short-term solution, hopefully it’s
clear that it’s not a viable &lt;em&gt;long-term&lt;/em&gt; option.&lt;/p&gt;
&lt;p&gt;That leaves us with option 2, which is extra annoying, because like 2 minutes ago I said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Saying “just upgrade lol” is almost rudely dismissive of the complexity that’s potentially involved in mitigating this
pain point&lt;/p&gt;
&lt;p&gt;— Andrew, 2 minutes ago&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;so, instead I’m saying the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’m very sorry to say this dear stranger, but unfortunately, the best, or really least-bad, option available to solve
the issue of “incompatible and shared 3rd party dependencies” is to ensure that all 3rd party shared dependencies have
the same (or at least &lt;em&gt;definitely compatible&lt;/em&gt;) version in every project that shares them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The Fix&lt;/h2&gt;
&lt;h3&gt;Taking a Step Back Before Going Forward&lt;/h3&gt;
&lt;p&gt;If you take another look at the workspaces in the “Sad App” Demo above, you’ll notice that each workspace has a
&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; in which it declares its dependencies. In the &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; at the root of the monorepo, we tell our
package manager where to find child workspaces, and it handles the intricacies of “hoisting” everything it can from the
child workspaces into the root &lt;code class=&quot;language-text&quot;&gt;node_modules&lt;/code&gt; when you do an install (thus saving space on disk only keeping 1 copy of
any dependencies where it’s possible to do so)…there’s nothing new here, this is standard &lt;code class=&quot;language-text&quot;&gt;Node&lt;/code&gt; monorepo stuff and
it’s the exact structure you get out of tools like &lt;code class=&quot;language-text&quot;&gt;Turborepo&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Lerna&lt;/code&gt; when you initialize a brand new monorepo
project.&lt;/p&gt;
&lt;p&gt;Unfortunately, it’s this structure which ends up leading down the road to “The Problem” above. You see, by allowing each
child workspace to declare its own dependencies we’re opening the door for divergence from one workspace to another when
it comes to dependencies that they “share” - in fact when using this kind of project structure, I am to the point now
where I even push back on the nomenclature of “share” in that last statement.&lt;/p&gt;
&lt;p&gt;If two workspaces declare different &lt;em&gt;versions&lt;/em&gt; of the same &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt;, can we really say they “share” that
dependency? Your package manager is potentially going to end up resolving two &lt;em&gt;completely&lt;/em&gt; different sets of files to
satisfy each workspaces’ declared dependencies, and at runtime, the code that gets executed could be wildly different!
In my opinion, these are “shared” dependencies “in name only”.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ll keep using the term &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt; going forward, and all I mean by that is just divorcing the “name” of a
dependency from any particular version. The difference between “my project uses &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt;” and “my project uses
&lt;code class=&quot;language-text&quot;&gt;react@18&lt;/code&gt;”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not only that, but this structure of monorepo makes it harder even for diligent maintainers to keep the versions of a
given &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt; in sync between workspaces because as your project grows and you add more workspaces that also
need to use that same &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt;, you have more places to check and keep up to date. If you miss one or make a
mistake, well, bad news, your package manager isn’t going to complain at you about that because it’s perfectly happy and
capable of resolving more than one version, even though it’s not what you want to happen - and if you’re unlucky, you
won’t find out about the mistake or missed update until it explodes at runtime.&lt;/p&gt;
&lt;p&gt;Tools and scripting are definitely helpful here, at least with the mechanics of performing these updates, but tools
can’t safely do this on their own (yet) and one way or another a human needs to be involved in this maintenance task.
Mistakes &lt;em&gt;will happen&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Implementing a Solution&lt;/h3&gt;
&lt;p&gt;At long last, time to discuss how I now go about solving “The Problem”. The key insight for me was when I had that
thought from earlier about &lt;code class=&quot;language-text&quot;&gt;dependency names&lt;/code&gt;. Really, what I want is to have, for any given &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt;, &lt;strong&gt;1 and
only 1&lt;/strong&gt; version for the entire monorepo. I don’t even want the &lt;em&gt;opportunity&lt;/em&gt; for my child workspaces to declare
different versions of a given 3rd party &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It turns out, it’s really easy to accomplish that — just put &lt;em&gt;all&lt;/em&gt; of your 3rd party dependencies into your root
&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;, and &lt;em&gt;none&lt;/em&gt; of them into &lt;em&gt;any&lt;/em&gt; child workspace &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;! With this style of declaring workspace
dependencies, when a child workspace has some code that imports &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt;, the Node &lt;code class=&quot;language-text&quot;&gt;require()&lt;/code&gt; resolution logic will
traverse up the file tree until it gets to the root &lt;code class=&quot;language-text&quot;&gt;node_modules&lt;/code&gt; where it’ll always resolve to the &lt;em&gt;same exact&lt;/em&gt;
version of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt;, regardless of which child workspace did the import.&lt;/p&gt;
&lt;p&gt;In many cases, there are literally no additional consideration to make with regards to the &lt;em&gt;machinery&lt;/em&gt; of managing your
dependencies&lt;sup class=&quot;text-indigo-500 dark:text-fluencyy-400 font-bold&quot;&gt;**&lt;/sup&gt; and you no longer need to worry
about &lt;code class=&quot;language-text&quot;&gt;ui-lib&lt;/code&gt; using a version of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; that’s incompatible with &lt;code class=&quot;language-text&quot;&gt;webapp&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;marketing-website&lt;/code&gt;…because they use
the exact same version &lt;em&gt;always&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;i class=&quot;text-sm lg:text-base&quot;&gt;&lt;sup class=&quot;text-indigo-500 dark:text-fluencyy-400 font-bold&quot;&gt;**&lt;/sup&gt;oh, for sure
there are more considerations overall, namely the tradeoffs involved here (and we’ll talk about those at the end), but I
just mean with respect to the mechanics of ensuring you use one version everywhere&lt;/i&gt;&lt;/p&gt;
&lt;h3&gt;“Happy App” Demonstration&lt;/h3&gt;
&lt;div class=&quot;w-full hidden md:block&quot;&gt;
	&lt;iframe
		loading=&quot;lazy&quot;
		class=&quot;w-full&quot;
		style=&quot;aspect-ratio: 16/9;&quot;
		src=&quot;https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-happy-app?embed=1&amp;file=README.md&amp;hideNavigation=1&quot;&gt;
	&lt;/iframe&gt;
	&lt;p class=&quot;px-4 py-2 text-sm lg:text-base leading-snug bg-white text-gray-600 border border-gray-300 rounded-b-md dark:bg-fluencyy-900 dark:text-fluencyy-100 dark:border-fluencyy-700&quot;&gt;
		&lt;b&gt;FYI&lt;/b&gt; — as of this writing, StackBlitz doesn&apos;t (can&apos;t) &lt;i&gt;fully&lt;/i&gt; support embedded WebContainers in anything 
		except Chromium-based browsers. If you&apos;re in Firefox, Safari, or something else non-Chromium, you can still 
		read the project code right here but unfortunately the in-page preview won&apos;t run and you&apos;ll have to 
		&lt;a href=&quot;https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-happy-app&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;
			open the &quot;Happy App Monorepo&quot; demo directly
		&lt;/a&gt; to run it. Sorry &apos;bout that!
	&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;w-full md:hidden&quot;&gt;
	&lt;blockquote&gt;
		&lt;b&gt;Hey!&lt;/b&gt; — right here on larger screens there is an embedded demo using StackBlitz, but to be effective it needs a little more space than 
		your device provides. You&apos;ll be better served by opening the demo directly in another browser tab:
		&lt;a href=&quot;https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-happy-app&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;
			https://stackblitz.com/github/andrewbrey/blog-monorepo-deps-happy-app
		&lt;/a&gt;
	&lt;/blockquote&gt;
&lt;/div&gt;
&lt;h2&gt;Discussion&lt;/h2&gt;
&lt;p&gt;In the “Happy App” demo, you see that &lt;em&gt;any&lt;/em&gt; 3rd party dependency that &lt;em&gt;any&lt;/em&gt; of the child workspaces needs to function is
just tossed into the root &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; under the &lt;code class=&quot;language-text&quot;&gt;dependencies&lt;/code&gt; key.&lt;/p&gt;
&lt;details class=&quot;px-3 py-1 bg-smoke-100 dark:bg-smoke-800 rounded select-none&quot;&gt;
	&lt;summary class=&quot;font-bold&quot;&gt;Care about why I only use the &quot;dependencies&quot; key?&lt;/summary&gt;
	&lt;p&gt;
		It&apos;s because, from the perspective of the workspace root, there&apos;s no way to know if a given dependency is for
		runtime or author-time...they&apos;re all just &quot;dependencies&quot; of one child workspace or another.
	&lt;/p&gt;
	&lt;p&gt;
		It also helps simplify some tools that need to look up dependency versions by their name (discussed further 
		down in this section)
	&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Further, each child workspace declares &lt;em&gt;no&lt;/em&gt; 3rd party dependencies directly. They &lt;em&gt;do declare&lt;/em&gt; 1st party “workspace”
dependencies which enables tools like &lt;code class=&quot;language-text&quot;&gt;Turborepo&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Nx&lt;/code&gt; to build a relationship graph between workspaces and optimize
task running, but technically, even these “workspace” dependencies could be omitted.&lt;/p&gt;
&lt;p&gt;You might also notice a strange &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt; key in the child workspaces &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;, under which you’ll see a structure
that looks suspiciously like the top level “dependency” declaration keys, except instead of being an object declaring
names with versions, it’s just an array declaring names.&lt;/p&gt;
&lt;p&gt;This declaration of &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt; dependencies (just a name I chose and made sense in my head) lets each child workspace keep
track of exactly which 3rd party packages it depends on, both at runtime as well as author-time, and further enables
construction of the &lt;code class=&quot;language-text&quot;&gt;&amp;quot;effective&amp;quot; package.json&lt;/code&gt; for a given workspace (by looking up the declared &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt; dependencies
in the root &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; which knows the concrete version information).&lt;/p&gt;
&lt;p&gt;Depending on what you’re building and in particular, what your build &lt;em&gt;tooling&lt;/em&gt; looks like, construction of the
&lt;code class=&quot;language-text&quot;&gt;&amp;quot;effective&amp;quot; package.json&lt;/code&gt; might not matter for you. For &lt;a href=&quot;https://lemmyapp.com&quot;&gt;the Lemmy App&lt;/a&gt;, one of the child
workspaces contains an &lt;code class=&quot;language-text&quot;&gt;Electron&lt;/code&gt;-based desktop app, and I’m using the &lt;code class=&quot;language-text&quot;&gt;electron-builder&lt;/code&gt; package to compile the app
into its distributable form for production builds.&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;code class=&quot;language-text&quot;&gt;electron-builder&lt;/code&gt; doesn’t play seamlessly with the ”&lt;em&gt;go look in the root of the workspace to find all
dependencies&lt;/em&gt;” idea out of the box when building for production, so I use the ability to construct an
&lt;code class=&quot;language-text&quot;&gt;&amp;quot;effective&amp;quot; package.json&lt;/code&gt; during production builds to perform a workspace-local install of my production runtime
dependencies and allow &lt;code class=&quot;language-text&quot;&gt;electron-builder&lt;/code&gt; to be non-the-wiser about how dependencies are managed in the monorepo
writ-large.&lt;/p&gt;
&lt;p&gt;The monorepo for &lt;code class=&quot;language-text&quot;&gt;the Lemmy App&lt;/code&gt; also includes some projects that use &lt;code class=&quot;language-text&quot;&gt;Vite&lt;/code&gt; for builds, and these work seamlessly out
of the box with my dependency management strategy, without the need to do anything with the &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt; dependency concept.
In fact, during development, even the desktop app uses &lt;code class=&quot;language-text&quot;&gt;Vite&lt;/code&gt; so for me the &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt; dependencies only come into play
when I build that one workspace for production in CI.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BTW, if you want to see the tooling I made to work with this &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt; dependency concept, take another look at the
“Happy App” demo code in the &lt;code class=&quot;language-text&quot;&gt;scripts&lt;/code&gt; directory at &lt;code class=&quot;language-text&quot;&gt;shadow.ts&lt;/code&gt;
(&lt;a href=&quot;https://github.com/andrewbrey/blog-monorepo-deps-happy-app/blob/main/scripts/shadow.ts&quot;&gt;or take a look on GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Outcomes and Tradeoffs&lt;/h3&gt;
&lt;h4&gt;Outcomes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;There is a single canonical version of every single &lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt; for the entire monorepo - “The Problem” from
above is not possible.&lt;/li&gt;
&lt;li&gt;It’s easy to adopt the solution and requires no ongoing curation of your child workspace dependency versions to keep
them in sync.&lt;/li&gt;
&lt;li&gt;Guaranteed to minimize disk space usage and install time for the monorepo because there’s only 1 version of each
&lt;code class=&quot;language-text&quot;&gt;dependency name&lt;/code&gt; and they all live in the root.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Tradeoffs&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You now &lt;strong&gt;must&lt;/strong&gt; deal with &lt;em&gt;any and all&lt;/em&gt; version migrations/breaking changes across your entire fleet of child
workspaces all at once when you upgrade a dependency.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For my own projects, I find this to be a positive tradeoff. If this project lives for a while, I’m going to be
handling those breaking changes eventually anyway, and if I just handle them all at once, it’s easier and then I can
forget about them rather than have to keep the steps of the upgrade in my working memory for longer. That said, if
my typical project had dozens of complex apps and doing an upgrade of &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; in my monorepo meant committing to an
hour of changes for each of them before I can be productive again, I would definitely consider if the risks of “The
Problem” as outlined above are acceptable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You now may need to choose solutions or 3rd party dependencies more carefully because you can’t (shouldn’t) massage
versions on a single workspace.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Technically, if you really needed to, you could still have child-workspace-level 3rd party dependencies declared
that could bypass the pain point of 3rd party dependencies that don’t play well together at the versions enforced by
your &lt;code class=&quot;language-text&quot;&gt;only 1 version allowed&lt;/code&gt; rule. That has not come up for me, but if it did, I think I would prefer a solution
that uses a different 3rd party dependency altogether (or a vendored/&lt;code class=&quot;language-text&quot;&gt;patch-package&lt;/code&gt;‘d dependency) before I
introduced an exception to my &lt;code class=&quot;language-text&quot;&gt;only 1 version allowed&lt;/code&gt; rule.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For this solution to be effective, you &lt;em&gt;should&lt;/em&gt; put safeguards in place to ensure nobody accidentally adds a 3rd party
dependency to a child workspace directly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I wrote both a custom &lt;code class=&quot;language-text&quot;&gt;ESLint&lt;/code&gt; rule and a &lt;code class=&quot;language-text&quot;&gt;git pre-commit&lt;/code&gt; hook to guard against this at author-time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;I started thinking about this topic when I began building &lt;a href=&quot;https://lemmyapp.com&quot;&gt;the Lemmy App&lt;/a&gt; which was the first
project I had ever started that used a monorepo. Admittedly, “The Problem” as outlined above wasn’t something I actually
encountered, just something I felt confident I &lt;em&gt;would&lt;/em&gt; encounter eventually and so I wanted a solution to it in hand.&lt;/p&gt;
&lt;p&gt;I thought a lot about it and came up with the solution outlined in this post and implemented it without doing much
outside research as to the “state of the art” on the topic.&lt;/p&gt;
&lt;p&gt;Since then I have discovered that &lt;code class=&quot;language-text&quot;&gt;Nx&lt;/code&gt; actually has thought a lot about this topic too (not surprising) and that they
even have terminology to ascribe to the strategies I talk about in this post;
&lt;a href=&quot;https://nx.dev/getting-started/package-based-repo-tutorial&quot;&gt;Package Based Monorepo&lt;/a&gt; is the name that they use to refer
to a monorepo with the dependency management strategy like that of the “Sad App” in my post. Then,
&lt;a href=&quot;https://nx.dev/getting-started/integrated-repo-tutorial&quot;&gt;Integrated Monorepo&lt;/a&gt; is the name that they use to refer to a
monorepo with canonicalized dependency versions as in the “Happy App” in my post.&lt;/p&gt;
&lt;p&gt;I was pretty chuffed to learn that they even have the same &lt;code class=&quot;language-text&quot;&gt;&amp;quot;effective&amp;quot; package.json&lt;/code&gt; concept in their
&lt;code class=&quot;language-text&quot;&gt;integrated monorepo&lt;/code&gt; structure, though they construct it by parsing your source files rather than relying on a &lt;code class=&quot;language-text&quot;&gt;shadow&lt;/code&gt;
dependency manifest.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note, I am not an expert on the &lt;code class=&quot;language-text&quot;&gt;Nx&lt;/code&gt; flavors of monorepo, so I am probably missing a lot of details here. I just
thought it was interesting continued reading on the topic!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;I do not think that the steps taken in this post are right, or even necessary, for &lt;em&gt;every&lt;/em&gt; monorepo out there, but with
how hot the topic of monorepos is right now, I do think that it’s important that potential monorepo pitfalls receive due
consideration. Hopefully now, after reading, you’re slightly better prepared to deal with a potential issue in your own
monorepo. Thanks for reading!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>17 min read</reading_time></item><item><title><![CDATA[Building a cURL-able Developer Resume Site with Remix]]></title><description><![CDATA[A little experiment in how to build a developer resume website with Remix, deployed to Vercel, that looks great in both the browser and when fetched with cURL.]]></description><link>https://blog.andrewbrey.com/2022-04-05-building-a-curl-able-developer-resume-site-with-remix/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2022-04-05-building-a-curl-able-developer-resume-site-with-remix/</guid><category><![CDATA[Tinker]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Tue, 05 Apr 2022 23:13:34 GMT</pubDate><content:encoded>&lt;p&gt;A couple of weeks ago I wanted to give building something with &lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt; a shot, so I made a small
application called &lt;a href=&quot;https://github.com/andrewbrey/wael&quot;&gt;WAEL&lt;/a&gt; for tracking my weight and exercise over time. While
building WAEL I was absolutely blown away by Remix! It was an absolute delight to use and the abstractions felt so
simple and intuitive. I’ve used a lot of frameworks and tools in my time as a web developer, and really, nothing has
impressed me as much upon first use as Remix has so far.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BTW, maybe I’ll make a separate post talking about WAEL at some point, but in the mean time, it’s open source so if
you’re interested feel free to just poke around in the repository —&gt;
&lt;a href=&quot;https://github.com/andrewbrey/wael&quot;&gt;https://github.com/andrewbrey/wael&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since I finished &lt;em&gt;that&lt;/em&gt; project my brain has been churning so much with ideas of things I could build with Remix, and
yesterday I had the idea that it would be super cool to build a resume site with Remix that was as you expect when you
visit it in the browser, but has a beautiful terminal output when you visit it with &lt;code class=&quot;language-text&quot;&gt;cURL&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;🎉 Here it is! &lt;code class=&quot;language-text&quot;&gt;Remix Curl Demo&lt;/code&gt; 🎉&lt;/h2&gt;
&lt;p&gt;Check it out at &lt;a href=&quot;https://remix-curl.vercel.app&quot;&gt;https://remix-curl.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can visit this page in the browser and it will serve up a static &lt;code class=&quot;language-text&quot;&gt;text/html&lt;/code&gt; response (which you might imagine is
your resume content) and you can &lt;em&gt;also&lt;/em&gt; &lt;code class=&quot;language-text&quot;&gt;curl&lt;/code&gt; this page in your terminal and see a beautiful &lt;code class=&quot;language-text&quot;&gt;text/plain&lt;/code&gt; response that
contains style ansi escape codes that your terminal can use to print a nice interface.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Of course, the code is fully open source and &lt;code class=&quot;language-text&quot;&gt;MIT&lt;/code&gt; licensed, so check that out on GitHub —&gt;
&lt;a href=&quot;https://github.com/andrewbrey/remix-curl&quot;&gt;andrewbrey/remix-curl&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As I expected this was honestly pretty trivial to put together and I think that really speaks to the seamlessness of the
abstractions that the Remix team has built. I spent more time getting &lt;code class=&quot;language-text&quot;&gt;Vercel&lt;/code&gt; to play nicely with my use of
&lt;code class=&quot;language-text&quot;&gt;patch-package&lt;/code&gt; than I did actually building the site!&lt;/p&gt;
&lt;p&gt;I could totally imagine building a fully fledged resume that renders beautifully in both the browser and in a terminal,
and, while pretty dumb and mostly pointless, who knows what hiring decision some manager might make to hire you because
you impress them with your silly &lt;code class=&quot;language-text&quot;&gt;cURL-able&lt;/code&gt; resume!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>2 min read</reading_time></item><item><title><![CDATA[Flōt - Keep a website always-on-top and translucent]]></title><description><![CDATA[Flōt lets you place a website on top of other windows, but allows you to see through to the content underneath. The perfect amount of distraction.]]></description><link>https://blog.andrewbrey.com/2021-09-06-flōt-keep-a-website-always-on-top-and-translucent/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2021-09-06-flōt-keep-a-website-always-on-top-and-translucent/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Mon, 06 Sep 2021 16:43:17 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve followed along with my blog post history or my projects &lt;a href=&quot;https://github.com/andrewbrey&quot;&gt;in GitHub&lt;/a&gt;, you might
already know that I tend to work best when I’m like…12% distracted by something - that’s part of why I made
&lt;a href=&quot;https://chrome.google.com/webstore/detail/nix-the-simple-noise-mixe/okhnofjkdbkfhkfmlggbnghhfeimfdhm&quot;&gt;the Nix browser extension&lt;/a&gt;
(see the accompanying &lt;a href=&quot;https://blog.andrewbrey.com/2021-02-06-nix-the-simple-noise-mixer/&quot;&gt;blog post about it here&lt;/a&gt;),
and it’s why I recently took a liking to watching tv shows in a teeny-tiny little browser window in the corner of my
screen while I work.&lt;/p&gt;
&lt;p&gt;As I have done this however, I couldn’t help wishing that the browser tab with a show in it was slightly less
noticeable, and if I completely had my druthers, if it were translucent so I could see what was beneath it. I looked
around the internet and really only found one app which promised to provide these features and that was the
&lt;a href=&quot;https://github.com/kamranahmedse/pennywise&quot;&gt;Pennywise app&lt;/a&gt;, but as best as I can tell it’s unmaintained (or at most
minimally) and either way it didn’t work correctly on my computer which runs Linux.&lt;/p&gt;
&lt;p&gt;I decided to take it on myself to whip up a weekend project to scratch my own itch, and from that, an app was born!&lt;/p&gt;
&lt;h2&gt;🎉 Announcing &lt;code class=&quot;language-text&quot;&gt;Flōt - Keep a website always-on-top and translucent&lt;/code&gt; 🎉&lt;/h2&gt;
&lt;p&gt;You can find it for download on the official website &lt;a href=&quot;https://flot.page&quot;&gt;https://flot.page&lt;/a&gt;. It supports Mac, Windows,
and Linux and works a treat.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key features include&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adjustable transparency&lt;/li&gt;
&lt;li&gt;Clean and minimal interface&lt;/li&gt;
&lt;li&gt;“Ignore clicks” mode which causes the window to ignore your mouse and allow you to click what is behind it - pretty
handy!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There were some surprisingly interesting challenges to solve with this tiny little app, and one of the big ones was how
to both (a) not break the adjustable transparency for any platform while (b) being able to view most sites.&lt;/p&gt;
&lt;p&gt;The problem statement here is that transparency of apps in Electron is somewhat tricky and breakage-prone. If you Google
around, you’ll find &lt;em&gt;tons&lt;/em&gt; of issues people have made about transparency not working in their app, especially on Linux.
Many app developers just say “meh, my target audience isn’t Linux, so I don’t care” but for me, since &lt;em&gt;I am my target
audience&lt;/em&gt; and I &lt;em&gt;do use Linux&lt;/em&gt; this was a must have! It turns out that the simplest way to ensure transparency works for
the embedded content is to put it in an &lt;code class=&quot;language-text&quot;&gt;iframe&lt;/code&gt; so that the child site can be styled like any other piece of DOM…but
now the child is in an &lt;code class=&quot;language-text&quot;&gt;iframe&lt;/code&gt; which means you’re dealing with a whole mess of web security and sandboxing topics for
the embedded content that you don’t normally have to mess with when a page is the top frame. Many sites, especially ones
a user of this app might be interested in viewing, have same-site restrictions for when they are placed in an &lt;code class=&quot;language-text&quot;&gt;iframe&lt;/code&gt;
and many also have very restrictive &lt;code class=&quot;language-text&quot;&gt;x-frame-options&lt;/code&gt; headers applied when their content is served. All of this lead
initially to being unable to display a lot of sites like YouTube and Twitch and Vimeo within Flōt.&lt;/p&gt;
&lt;p&gt;I could get around this by making the target site the top frame, which in Electron terms would mean giving it its own
&lt;code class=&quot;language-text&quot;&gt;BrowserWindow&lt;/code&gt; object, essentially another Chromium tab, within the app but if I did this, I would only have control of
transparency to the extent made possible by Electron for &lt;code class=&quot;language-text&quot;&gt;BrowserWindow&lt;/code&gt; objects, which is to say, none (on Linux
anyway). For Mac and Windows, Electron can do adjustable transparency of the window, but for Linux your either are fully
transparent or you are not. In the &lt;code class=&quot;language-text&quot;&gt;iframe&lt;/code&gt; world, I could just make the app window transparent for all platforms, and
use the transparency of the DOM to control just how see-through the content is.&lt;/p&gt;
&lt;p&gt;In the end, I decided to keep the initial design, using &lt;code class=&quot;language-text&quot;&gt;iframes&lt;/code&gt; since the adjustable transparency was a key feature
for me, and I used request interception in the Electron main process to ignore certain security features browsers are
supposed to enforce, such as &lt;code class=&quot;language-text&quot;&gt;x-frame-options&lt;/code&gt; headers and &lt;code class=&quot;language-text&quot;&gt;content-security-policy&lt;/code&gt;. This makes it so sites &lt;em&gt;can be
embedded&lt;/em&gt; (mostly, there are some things which still break it seems) and I also get to keep my transparency controls in
the DOM rather than on the native window…just, at the expense of pretty reduced browsing security.&lt;/p&gt;
&lt;p&gt;Something had to give to meet my goals for the app, and I decided that this app was a toy which wasn’t meant to be used
as a regular browser - instead I use it is a “read only” window for publicly accessible pages where I don’t enter my
credentials or any sensitive information into the sites I visit. For this purpose, it works quite well and it was very
fun to build, so I think all-in-all it was worth the few days of diversion!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>5 min read</reading_time></item><item><title><![CDATA[PWPR - The simple Playwright Pre-Render CLI]]></title><description><![CDATA[The simplicity of making an HTTP request with cURL but with the ability to run and render JS powered web pages]]></description><link>https://blog.andrewbrey.com/2021-08-03-pwpr-the-simple-playwright-pre-render-cli/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2021-08-03-pwpr-the-simple-playwright-pre-render-cli/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Tue, 03 Aug 2021 22:07:16 GMT</pubDate><content:encoded>&lt;p&gt;Recently, while working on automating the installation and setup of my computer I encountered an annoying situation. I
was trying to script out the installation of Dropbox, and I didn’t want to hard code in an installer version - for all
other tools and applications that I “manually” install with scripts I’ve been able to obtain explicit version
information programmatically. For some I could get version information via dedicated webpages (e.g. &lt;code class=&quot;language-text&quot;&gt;golang&lt;/code&gt; which
provides its &lt;a href=&quot;https://golang.org/VERSION?m=text&quot;&gt;current version here&lt;/a&gt;). For others, which host their release assets on
GitHub I could use the GitHub Releases API (e.g. the &lt;code class=&quot;language-text&quot;&gt;starship&lt;/code&gt; shell prompt,
&lt;a href=&quot;https://api.github.com/repos/starship/starship/releases/latest&quot;&gt;which uses GitHub Releases&lt;/a&gt;). In fact, up until
Dropbox, the only tool which made this even &lt;em&gt;a little&lt;/em&gt; difficult was the &lt;code class=&quot;language-text&quot;&gt;aws-cli&lt;/code&gt; which required that I parse their
&lt;a href=&quot;https://raw.githubusercontent.com/aws/aws-cli/v2/CHANGELOG.rst&quot;&gt;CHANGELOG file&lt;/a&gt; to figure out the latest version.&lt;/p&gt;
&lt;p&gt;For Dropbox though, the only good way I could programmatically discover their latest installer version was via a link
present on the download page, which for me being on Linux (&lt;a href=&quot;https://pop.system76.com/&quot;&gt;Pop!_OS specifically&lt;/a&gt;) is
&lt;a href=&quot;https://www.dropbox.com/install-linux&quot;&gt;https://www.dropbox.com/install-linux&lt;/a&gt;. If you go there, you might think ”&lt;em&gt;ok
seems easy enough, just curl/wget the page and grab the href for the .deb file download link with grep&lt;/em&gt;” and that is
indeed what I tried at first…then I realized that this &lt;em&gt;incredibly simple and text-only&lt;/em&gt; page is completely
javascript rendered, meaning that curl-ing it is useless as I will only get back the pre-javascript-render page shell.&lt;/p&gt;
&lt;p&gt;As annoying as this was, I didn’t just settle for hard coding in the version for this one thing, and instead I looked
around for a CLI wrapper for either &lt;a href=&quot;https://playwright.dev&quot;&gt;Playwright&lt;/a&gt; or &lt;a href=&quot;https://pptr.dev/&quot;&gt;Puppeteer&lt;/a&gt; which are
tools I’ve used extensively before to do browser automation via headless chromium, firefox, and webkit. Either of these
would make it possible for me to visit the page, render the page with JS, then get the final page HTML. As it turns out,
Playwright does have a first-party CLI, but amazingly to me, it’s mostly geared towards PDF creation and automation of
writing Playwright procedures - not prerendering…this seems like super low hanging fruit, but ahhhh well.&lt;/p&gt;
&lt;p&gt;Aaaanyway, I decided just to make my own wrapper for Playwright, so here it is!&lt;/p&gt;
&lt;h2&gt;🎉 Announcing &lt;code class=&quot;language-text&quot;&gt;PWPR - The simple Playwright Pre-Render CLI&lt;/code&gt; 🎉&lt;/h2&gt;
&lt;p&gt;You can find it on NPM at &lt;a href=&quot;https://www.npmjs.com/package/pwpr&quot;&gt;https://www.npmjs.com/package/pwpr&lt;/a&gt;. You don’t need to
install it to use it (in fact, I don’t recommend that you &lt;em&gt;do&lt;/em&gt; install it) - simply run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx pwpr --url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;example.com --output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;example.html&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;doing this will fetch &lt;code class=&quot;language-text&quot;&gt;https://example.com&lt;/code&gt;, wait for it to fully load, including running the page’s javascript, then
output the rendered HTML of the page to &lt;code class=&quot;language-text&quot;&gt;example.html&lt;/code&gt; in your current working directory - it’s like the simplicity of
cURL, but with the ability to run the JS of the page you’re fetching.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Of course, the source code is fully open source and &lt;code class=&quot;language-text&quot;&gt;MIT&lt;/code&gt; licensed, so check that out on GitHub —&gt;
&lt;a href=&quot;https://github.com/andrewbrey/pwpr&quot;&gt;andrewbrey/pwpr&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With this, I’ve been able to keep my Dropbox install simple and programmatic, without any version hard coding. Hopefully
someone else out there finds it useful!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>3 min read</reading_time></item><item><title><![CDATA[NPM Global Audit]]></title><description><![CDATA[Making it possible to run npm audit against globally installed node modules]]></description><link>https://blog.andrewbrey.com/2021-06-26-npm-global-audit/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2021-06-26-npm-global-audit/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Sat, 26 Jun 2021 21:36:15 GMT</pubDate><content:encoded>&lt;p&gt;I’ve found that it’s a good idea to keep as much of the software what I use every day up to date, and that includes the
handful of node modules I have installed globally on my system - this is a pretty short list with “generally useful”
things like &lt;a href=&quot;https://www.npmjs.com/package/rimraf&quot;&gt;rimraf&lt;/a&gt;, &lt;a href=&quot;https://www.npmjs.com/package/time-tracker&quot;&gt;time-tracker&lt;/a&gt;,
&lt;a href=&quot;https://www.npmjs.com/package/live-server&quot;&gt;live-server&lt;/a&gt; (plus a few others). Towards the goal of keeping things up to
date, I have made running &lt;a href=&quot;https://www.npmjs.com/package/npm-check-updates&quot;&gt;npm-check-updates&lt;/a&gt; against my various
packages a habit, and this includes doing a daily update of all the modules I have installed globally. When you do an
update against your globally installed modules, just as you get with a locally installed module, the &lt;code class=&quot;language-text&quot;&gt;npm&lt;/code&gt; cli will
usually spit out some warnings about vulnerabilities in the packages you have installed and will then tell you to run
&lt;code class=&quot;language-text&quot;&gt;npm audit&lt;/code&gt; to learn more…&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;Well, if you try and run &lt;code class=&quot;language-text&quot;&gt;npm audit -g&lt;/code&gt; or something similarly sane to attempt an audit against your globally installed
packages, you learn that &lt;code class=&quot;language-text&quot;&gt;npm&lt;/code&gt; won’t do that for you. I don’t fully understand the rationale for this limitation, but
whaaaatever, I’ve decided to address the limitation myself!&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;I have released &lt;a href=&quot;https://www.npmjs.com/package/npm-global-audit&quot;&gt;npm-global-audit&lt;/a&gt;, an &lt;code class=&quot;language-text&quot;&gt;MIT&lt;/code&gt; licensed and
&lt;a href=&quot;https://github.com/andrewbrey/npm-global-audit&quot;&gt;fully open source&lt;/a&gt; node module which can perform an &lt;code class=&quot;language-text&quot;&gt;npm audit&lt;/code&gt; of your
globally installed node modules. You don’t even need to install it to use it (in fact, I don’t recommend that you &lt;em&gt;do&lt;/em&gt;
install it) - simply run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx npm-global-audit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;from your terminal, and away it goes! There is more detail about how it works in the &lt;code class=&quot;language-text&quot;&gt;README&lt;/code&gt; for the project, so feel
free to have a poke about to learn more. Hopefully this will help someone out there who likes to stay aware of the
outstanding issues present in their software, at least for javascript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>2 min read</reading_time></item><item><title><![CDATA[Nix — The Simple Noise Mixer]]></title><description><![CDATA[I built a simple brower extension to add a background / "white noise" machine to Chrome and Firefox]]></description><link>https://blog.andrewbrey.com/2021-02-06-nix-the-simple-noise-mixer/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2021-02-06-nix-the-simple-noise-mixer/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Sat, 06 Feb 2021 23:07:37 GMT</pubDate><content:encoded>&lt;p&gt;When I’m working at my computer, I really struggle to get into a flow and be productive if I don’t have &lt;em&gt;something&lt;/em&gt;
playing in my headphones. Usually that something is music, but lately I’ve been really into &lt;code class=&quot;language-text&quot;&gt;soundscape&lt;/code&gt; types of
background noise, like the kind you get in white noise apps and websites. I used a few different sites for this, but
with each, I kept finding myself wanting for the noise mixer to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a standalone thing I could control with keyboard shortcuts, not another website tab&lt;/li&gt;
&lt;li&gt;usable offline for when I’m working without internet&lt;/li&gt;
&lt;li&gt;simpler and easier to make a mixture of sounds that would be my “preferred” mix, which would be remembered when I came
back&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basically I wanted to be able to turn on my favorite mixture of &lt;code class=&quot;language-text&quot;&gt;rain + fire crackling + thunder&lt;/code&gt; every time, with as
few clicks as possible, even if I don’t have internet.&lt;/p&gt;
&lt;p&gt;None of the sites I found ticked all these boxes, so I decided I would make it on my own. I decided to do so as a
browser extension to take advantage of the fact that it elevates the functionality to an “always there, ready when you
are” status. It also made it super simple to remember the most recent mix I had playing, and more generally, I find it
really fun to make extensions for the browser!&lt;/p&gt;
&lt;h2&gt;🎉 Announcing &lt;code class=&quot;language-text&quot;&gt;Nix - the simple noise mixer&lt;/code&gt; 🎉&lt;/h2&gt;
&lt;p&gt;You can find it on both the
&lt;a href=&quot;https://chrome.google.com/webstore/detail/nix-the-simple-noise-mixe/okhnofjkdbkfhkfmlggbnghhfeimfdhm&quot;&gt;Chrome Web Store&lt;/a&gt;
and the &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/nix-the-simple-noise-mixer/&quot;&gt;Firefox Add-ons Store&lt;/a&gt;!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Of course, the extension code is fully open source and &lt;code class=&quot;language-text&quot;&gt;MIT&lt;/code&gt; licensed, so check that out on GitHub —&gt;
&lt;a href=&quot;https://github.com/andrewbrey/nix&quot;&gt;andrewbrey/nix&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was super fun and quick to build, and here are some highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I used &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; for the UI. I’ve really come to enjoy working in &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; especially with the way that you’re are
incentivized so early to make small component abstractions with almost no overhead - I really enjoy this over the
”&lt;em&gt;ugh, I guess I need a whole new file for this thing, and what folder should it go into??&lt;/em&gt;” questions that come with
&lt;code class=&quot;language-text&quot;&gt;Angular&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Vue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I used &lt;a href=&quot;https://activejs.dev/#/&quot;&gt;ActiveJS&lt;/a&gt; for observable state management, and it was positively delightful! I have
been itching to give it a try for a while now, and I found it’s blend of &lt;code class=&quot;language-text&quot;&gt;rxjs&lt;/code&gt; observables (which I mostly liked from
my time doing &lt;code class=&quot;language-text&quot;&gt;Angular&lt;/code&gt;) with the &lt;code class=&quot;language-text&quot;&gt;redux&lt;/code&gt;-like store-with-actions model to be such a nice middle ground between the
different state solutions I’ve used. It you’ve never heard of &lt;code class=&quot;language-text&quot;&gt;ActiveJS&lt;/code&gt; before, I strongly recommend poking around in
their docs and learning all it can do, it’s really neat.&lt;/li&gt;
&lt;li&gt;I used &lt;code class=&quot;language-text&quot;&gt;Tailwind CSS&lt;/code&gt; - at this point, I don’t imagine I’ll ever use anything else for styling on a personal project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I really enjoy building browser extensions, and this one didn’t break that trend - it’s quite simple, but with just
enough surface area to have presented a challenge or two, mostly around picking the right architecture and message
passing controls exposed to the popup by the background. Give it a try and see what you think - I find it quite nice
when I’m really trying to be productive 👍&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>3 min read</reading_time></item><item><title><![CDATA[Announcing the Lemmy App!]]></title><description><![CDATA[I've been working semi-secretively on a project called Lemmy - a desktop application that solves the pain point of sharing institutional knowledge with your team.]]></description><link>https://blog.andrewbrey.com/2020-08-13-announcing-lemmy/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2020-08-13-announcing-lemmy/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Thu, 13 Aug 2020 03:24:10 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Institutional Knowledge is an asset, not a liability&lt;/strong&gt; — at least, that’s what I want to be true, but unfortunately
for every company I’ve ever worked for, that hasn’t &lt;em&gt;quite&lt;/em&gt; worked out.&lt;/p&gt;
&lt;p&gt;In fact, I have personally only ever really observed the opposite; an engineer who was with the company at the very
beginning and “knows everything” decides to take another job and out the door with them walks years of knowledge. Most
of that knowledge ends up being stuff nobody even knew they had! That’s the nature of “institutional knowledge”… it
becomes so second nature that it is relegated to emails, slack threads, outdated docs, stale wiki pages, or most
commonly a single person’s brain. When that brain walks out the door, the hard-won lessons go with them.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://lemmyapp.com&quot;&gt;Lemmy App&lt;/a&gt; is a project I’ve been working on to solve this problem once and for all! I’m not
ready just yet to dive into the details, but there’s a bit of teaser content over on the marketing website. When you
take a look, make sure to sign up to be part of the beta program so you can be notified when the app is live!&lt;/p&gt;
&lt;p&gt;I’ll also be blogging on &lt;a href=&quot;https://lemmyapp.com/blog&quot;&gt;the Lemmy Blog&lt;/a&gt; about the process of building the app, so if you
want to see what it’s like to build a desktop app from scratch you can check in on there every so often to see what I’ve
been up to!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>2 min read</reading_time></item><item><title><![CDATA[Using TailwindCSS with Ionic 5 and Angular 10]]></title><description><![CDATA[How you can add TailwindCSS to an Ionic 5 and Angular 10 mobile app without ejecting or messing with the Angular webpack build]]></description><link>https://blog.andrewbrey.com/2020-07-06-using-tailwindcss-with-ionic-5-and-angular-10/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2020-07-06-using-tailwindcss-with-ionic-5-and-angular-10/</guid><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Mon, 06 Jul 2020 02:15:33 GMT</pubDate><content:encoded>&lt;p&gt;Recently for some client work I had to dig back into an &lt;a href=&quot;https://ionicframework.com/&quot;&gt;Ionic Framework&lt;/a&gt; mobile app code
base which I had most recently edited over a year ago. In the intervening time since my last commits in that project I
have seen the light of &lt;code class=&quot;language-text&quot;&gt;utility&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;functional&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;atomic&lt;/code&gt; CSS, thanks in no small part to
&lt;a href=&quot;https://twitter.com/adamwathan&quot;&gt;Adam Wathan&lt;/a&gt; the creator of &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;TailwindCSS&lt;/a&gt; and his
&lt;a href=&quot;https://adamwathan.me/css-utility-classes-and-separation-of-concerns/&quot;&gt;blog post from 2017&lt;/a&gt; where he talked about his
evolution from so called &lt;em&gt;semantic CSS&lt;/em&gt; to embracing utilities.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Another great discussion of the merits of utility CSS (and why you should be using it) can be found in
&lt;a href=&quot;https://twitter.com/johnpolacek&quot;&gt;John Polacek’s&lt;/a&gt; awesome presentation
&lt;a href=&quot;http://johnpolacek.com/rethinking/&quot;&gt;Rethinking CSS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is also a wonderful talk by Simon Vrachliotis from years ago called
&lt;a href=&quot;https://vimeo.com/294976504&quot;&gt;A Real-Life Journey Into the Opinionated World of “Utility-First” CSS&lt;/a&gt; which also does
an excellent job of showing the logical progression going from semantic css to utility css.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As I was working I found myself over and over &lt;em&gt;wishing so badly&lt;/em&gt; that the ergonomics of styling the prebuilt Ionic
components could be as pleasant as the experience of working with Tailwind, and then inspiration struck! &lt;strong&gt;&lt;em&gt;Why don’t
you just add Tailwind to the project dummy??&lt;/em&gt;&lt;/strong&gt;. I don’t know why it took such rude comments from myself to make me
think of simply adding Tailwind to make the styling experience nicer but hey, whatever it takes right?&lt;/p&gt;
&lt;h2&gt;Adding TailwindCSS to a vanilla Angular CLI project&lt;/h2&gt;
&lt;p&gt;Before talking about what it takes to add Tailwind to augment the prebuilt styles of Ionic components, I’ll first touch
on how I’ve been successful adding it to a plain ol’ Angular project, without having to &lt;code class=&quot;language-text&quot;&gt;eject&lt;/code&gt; the &lt;code class=&quot;language-text&quot;&gt;webpack&lt;/code&gt;
configuration from control of the Angular CLI.&lt;/p&gt;
&lt;p&gt;Since Angular doesn’t use the &lt;code class=&quot;language-text&quot;&gt;PostCSS&lt;/code&gt; loader with webpack, we can’t just tack another &lt;code class=&quot;language-text&quot;&gt;require&lt;/code&gt; onto the
&lt;code class=&quot;language-text&quot;&gt;postcss.config.js&lt;/code&gt; to include Tailwind. What we &lt;em&gt;can do&lt;/em&gt; however is simply compile our Tailwind directives ahead of
time with the &lt;code class=&quot;language-text&quot;&gt;tailwind&lt;/code&gt; node binary executed from &lt;code class=&quot;language-text&quot;&gt;npm&lt;/code&gt; scripts (or a tooling of choice) &lt;em&gt;before&lt;/em&gt; the Angular webpack
build starts such that the pre-built stylesheets can then be included in the webpack build as if they were regular CSS.&lt;/p&gt;
&lt;p&gt;If you’re using &lt;code class=&quot;language-text&quot;&gt;sass&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;scss&lt;/code&gt; this is even easier because you can leverage &lt;code class=&quot;language-text&quot;&gt;@import&lt;/code&gt; syntax in the &lt;code class=&quot;language-text&quot;&gt;scss&lt;/code&gt; compiler to
include the &lt;code class=&quot;language-text&quot;&gt;tailwind.css&lt;/code&gt; (or whatever you name your file) in your final bundle. If you’re not using &lt;code class=&quot;language-text&quot;&gt;scss&lt;/code&gt; you can
still make Angular aware of the file by pointing to it in your &lt;code class=&quot;language-text&quot;&gt;angular.json&lt;/code&gt; builder definitions for included styles.&lt;/p&gt;
&lt;p&gt;This works quite well but isn’t as convenient as we might like - in particular one thing we might want is for file
watchers on our source files to include the &lt;code class=&quot;language-text&quot;&gt;tailwind.config.js&lt;/code&gt; so that changes to our Tailwind config trigger a
rebuild of our Tailwind styles and subsequently the styles Angular knows about. It’s not always straight forward to
create this type of behavior but thankfully there’s a very nice little node module (isn’t there pretty much always?)
called &lt;a href=&quot;https://github.com/tehpsalmist/ng-tailwindcss&quot;&gt;ng-tailwindcss&lt;/a&gt; which not only gives you the setup I described
above, but also sets up file watchers for you that will rebuild your Tailwind styles whenever you change your Tailwind
config and ties the whole thing up in a nice little bow!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I won’t go into detail about how to set up &lt;code class=&quot;language-text&quot;&gt;ng-tailwindcss&lt;/code&gt; as it’s well covered by the helpful README - one thing to
point out is that it’s not required to use the integrated &lt;code class=&quot;language-text&quot;&gt;PurgeCSS&lt;/code&gt; from this package because Tailwind now includes a
first class &lt;code class=&quot;language-text&quot;&gt;PurgeCSS&lt;/code&gt; integration out of the box.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Adding TailwindCSS to Ionic 5 with Angular&lt;/h2&gt;
&lt;p&gt;Well it probably shouldn’t come as too much of a surprise that much of what was true for a vanilla Angular project is
still true for Ionic + Angular, because since Ionic 4, the framework shifted to Web Components and became decoupled from
Angular. In doing so, they largely got out of the business of project structure and defer to the chosen framework to
make those choices.&lt;/p&gt;
&lt;p&gt;That said, here’s what the default Ionic 5 with Angular &lt;code class=&quot;language-text&quot;&gt;global.scss&lt;/code&gt; looks like for a project scaffolded with the
&lt;code class=&quot;language-text&quot;&gt;blank&lt;/code&gt; starter:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scss&quot;&gt;&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
 * App Global CSS
 * ----------------------------------------------------------------------------
 * Put style rules here that you want to apply globally. These styles are for
 * the entire app and not just one component. Additionally, this file can be
 * used as an entry point to import other CSS/Sass files to be included in the
 * output CSS.
 * For more information on global stylesheets, visit the documentation:
 * https://ionicframework.com/docs/layout/global-stylesheets
 */&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Core CSS required for Ionic components to work properly */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/core.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Basic CSS for apps built with Ionic */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/normalize.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/structure.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/typography.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/display.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Optional CSS utils that can be commented out */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/padding.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/float-elements.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/text-alignment.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/text-transformation.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/flex-utils.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ll notice that there are &lt;em&gt;already some&lt;/em&gt; utility stylesheets included with the framework - I don’t want those since
I’m going to be using Tailwind and I also need to make sure that I include Tailwind’s version of normalize because it
does one or two things a reset doesn’t normally do, namely adding a default border color of &lt;code class=&quot;language-text&quot;&gt;gray-500&lt;/code&gt; that would
otherwise normally have a default border color of &lt;code class=&quot;language-text&quot;&gt;transparent&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you’re curious, this is so that you can simply add &lt;code class=&quot;language-text&quot;&gt;border&lt;/code&gt; to an element instead of having to do
&lt;code class=&quot;language-text&quot;&gt;border border-gray-500&lt;/code&gt; to make a default border visible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I also need to make sure that I don’t lose &lt;em&gt;too much&lt;/em&gt; from the Ionic styles because there are lots of style aspects that
I rely on for their Web Components. What I need on top of my normal &lt;strong&gt;Angular only&lt;/strong&gt; addition of Tailwind is to be able
to simulate splitting my Tailwind directives apart in the way recommended by the
&lt;a href=&quot;https://tailwindcss.com/docs/using-with-preprocessors&quot;&gt;Tailwind docs&lt;/a&gt; so that I can ensure the intended specificity is
applied to my Tailwind classes compared to those that come from Ionic.&lt;/p&gt;
&lt;p&gt;I decided to accomplish this by splitting the compilation of each directive, i.e. &lt;code class=&quot;language-text&quot;&gt;@base&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;@utilities&lt;/code&gt; and
&lt;code class=&quot;language-text&quot;&gt;@components&lt;/code&gt; into a separate prebuild step and thus import them into my &lt;code class=&quot;language-text&quot;&gt;global.scss&lt;/code&gt; in the correct relative positions
compared to the Ionic imports I was keeping. Something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scss&quot;&gt;&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
 * App Global CSS
 * ----------------------------------------------------------------------------
 * Put style rules here that you want to apply globally. These styles are for
 * the entire app and not just one component. Additionally, this file can be
 * used as an entry point to import other CSS/Sass files to be included in the
 * output CSS.
 * For more information on global stylesheets, visit the documentation:
 * https://ionicframework.com/docs/layout/global-stylesheets
 */&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Core CSS required for Ionic components to work properly */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/core.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 
 Use the Tailwind reset instead of the one 
 from Ionic because it sets some required 
 defaults for Tailwind
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./styles/tailwind/base.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Basic CSS for apps built with Ionic */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/structure.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/typography.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~@ionic/angular/css/display.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./styles/tailwind/components.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Optional CSS utils that can be commented out */&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// @import &apos;~@ionic/angular/css/padding.css&apos;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// @import &apos;~@ionic/angular/css/float-elements.css&apos;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// @import &apos;~@ionic/angular/css/text-alignment.css&apos;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// @import &apos;~@ionic/angular/css/text-transformation.css&apos;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// @import &apos;~@ionic/angular/css/flex-utils.css&apos;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./styles/tailwind/utilities.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Other normal imports...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./styles/abstracts/fonts&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that now I don’t use the Ionic utilities and I &lt;em&gt;do use&lt;/em&gt; all of the Tailwind goodness!&lt;/p&gt;
&lt;h3&gt;Configuration for ng-tailwindcss&lt;/h3&gt;
&lt;p&gt;Even though it’s not strictly necessary, I decided to keep &lt;code class=&quot;language-text&quot;&gt;ng-tailwindcss&lt;/code&gt; around simply because it provides a nice
abstraction on top of the &lt;code class=&quot;language-text&quot;&gt;tailwind&lt;/code&gt; cli. I created the following &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; scripts to invoke it &lt;strong&gt;3 separate
times&lt;/strong&gt;, once each for the Tailwind directives:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tailwind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;yarn tw:base &amp;amp;&amp;amp; yarn tw:utilities &amp;amp;&amp;amp; yarn tw:components&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tailwind:prod&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PURGE_TW=true yarn tailwind&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tw:base&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ngtw b -c ng-tailwind/ng-tailwind.base.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tw:utilities&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ngtw b -c ng-tailwind/ng-tailwind.utilities.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tw:components&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ngtw b -c ng-tailwind/ng-tailwind.components.js&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;For reasons of simply not wanting the lines to be quite long in my &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; I split the scripts apart into a
little namespace &lt;code class=&quot;language-text&quot;&gt;tw:&lt;/code&gt; and then made a pair of “parent” scripts for building everything for development and production
(in which purging will be invoked by the Tailwind cli).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You’ll notice that each call to &lt;code class=&quot;language-text&quot;&gt;ngtw&lt;/code&gt; in the 3 &lt;code class=&quot;language-text&quot;&gt;tw:&lt;/code&gt; namespaced scripts includes a &lt;code class=&quot;language-text&quot;&gt;-c&lt;/code&gt; flag that points to an
&lt;code class=&quot;language-text&quot;&gt;ng-tailwindcss&lt;/code&gt; configuration file. Each of the files looks something like&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// For example, the compilation of @base looks like this&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	configJS&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./tailwind.config.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	sourceCSS&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/styles/ng-tailwind/base.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	outputCSS&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/styles/tailwind/base.css&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	sass&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	purge&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In turn, you’ll see that each of these configuration files for &lt;code class=&quot;language-text&quot;&gt;ngtw&lt;/code&gt; points to a css file to compile and a css file to
output. I include the &lt;strong&gt;sourceCSS&lt;/strong&gt; file in &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt; as it pretty much just looks like&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;// ./src/styles/ng-tailwind/base.css
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@tailwind&lt;/span&gt; base&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I then &lt;code class=&quot;language-text&quot;&gt;git ignore&lt;/code&gt; the compiled files, e.g. &lt;code class=&quot;language-text&quot;&gt;./src/styles/tailwind/base.css&lt;/code&gt;, but it’s &lt;em&gt;these compiled files&lt;/em&gt; which are
included in my &lt;code class=&quot;language-text&quot;&gt;global.scss&lt;/code&gt; seen above. This allows me to split the Tailwind css files up just like they would be if
&lt;code class=&quot;language-text&quot;&gt;PostCSS&lt;/code&gt; did the compilation, and also ensures that I don’t include a massive Tailwind development css file in my
repo - woo!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adding to the Ionic dev server and production builds&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since this is all essentially just pre-compiling the css, adding to builds (development and production) is really just a
matter of invoking the script before invoking the other build commands - here’s what the full &lt;code class=&quot;language-text&quot;&gt;scripts&lt;/code&gt; key in my
&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;yarn tailwind &amp;amp;&amp;amp; ionic serve --no-open --lab --external -- --proxy-config proxy.conf.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tailwind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;yarn tw:base &amp;amp;&amp;amp; yarn tw:utilities &amp;amp;&amp;amp; yarn tw:components&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tailwind:prod&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PURGE_TW=true yarn tailwind&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;emulate&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;yarn clean &amp;amp;&amp;amp; yarn tailwind &amp;amp;&amp;amp; env-cmd --use-shell \&quot;ionic cordova emulate ios --buildConfig build.json -- -- --storePassword=$KEYSTORE_PASSWORD --password=$KEY_PASSWORD\&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;yarn clean &amp;amp;&amp;amp; yarn tailwind:prod &amp;amp;&amp;amp; env-cmd --use-shell \&quot;ionic cordova build ios --prod --release --device --buildConfig build.json -- -- --storePassword=$KEYSTORE_PASSWORD --password=$KEY_PASSWORD\&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tw:base&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ngtw b -c ng-tailwind/ng-tailwind.base.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tw:utilities&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ngtw b -c ng-tailwind/ng-tailwind.utilities.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tw:components&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ngtw b -c ng-tailwind/ng-tailwind.components.js&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Unifying Tailwind and Ionic&lt;/h3&gt;
&lt;p&gt;One other consideration is that you don’t want Tailwind to stomp all over the Ionic styles (most likely). You can help
this with some careful thought in your &lt;code class=&quot;language-text&quot;&gt;tailwind.config.js&lt;/code&gt;. Here’s the one I used in this project:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	purge&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		enabled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PURGE_TW&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;./src/**/*.html&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./src/**/*.ts&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	theme&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		extend&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		colors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			primary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-primary)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-primary-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-primary-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			secondary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-secondary)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-secondary-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-secondary-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			tertiary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-tertiary)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-tertiary-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-tertiary-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			light&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-light)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-light-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-light-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			medium&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-medium)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-medium-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-medium-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			dark&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-dark)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-dark-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-dark-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-success)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-success-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-success-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			warning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-warning)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-warning-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-warning-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			danger&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-danger)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				shade&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-danger-shade)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				tint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-danger-tint)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			step&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;50&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-50)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;100&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-100)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;150&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-150)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;200&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-200)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;250&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-250)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;300&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-300)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;350&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-350)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;400&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-400)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;450&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-450)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;500&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-500)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;550&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-550)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;600&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-600)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;650&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-650)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;700&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-700)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;750&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-750)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;800&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-800)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;850&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-850)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;900&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-900)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;token string&quot;&gt;&apos;950&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;var(--ion-color-step-950)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	variants&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	corePlugins&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		textOpacity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		backgroundOpacity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	plugins&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ll notice a few things, like&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I use the Ionic style custom properties instead of the corresponding hex/rgb values and further, I do this while
overriding the &lt;code class=&quot;language-text&quot;&gt;colors&lt;/code&gt; key from Tailwind. There was simply no need to compile all of the Tailwind colors when I had a
full palette from Ionic, and I could make Ionic’s configuration there be a single source of truth.&lt;/li&gt;
&lt;li&gt;I turned &lt;strong&gt;off&lt;/strong&gt; the &lt;code class=&quot;language-text&quot;&gt;textOpacity&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;backgroundOpacity&lt;/code&gt; plugins - this is the one real impact of not having the
process of “how styles are made” be unified. The color variables I have, due to how you configure styling in Ionic,
are either hex codes that are incompatible with CSS &lt;code class=&quot;language-text&quot;&gt;rgba&lt;/code&gt; functions (which is how the color opacity utilities work),
or a partial &lt;code class=&quot;language-text&quot;&gt;rgb&lt;/code&gt; value like &lt;code class=&quot;language-text&quot;&gt;255, 255, 255&lt;/code&gt; (but not &lt;code class=&quot;language-text&quot;&gt;rgb(255, 255, 255)&lt;/code&gt;) for white. This &lt;em&gt;would work&lt;/em&gt; with
Tailwind I think, but I decided that I didn’t want to declare these types of variables for all of my colors and
variations. There’s just so many colors to specify variables for in Ionic, and I didn’t want to bother for the sake of
some translucent text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Overall not that bad right? Once you make the connection that Tailwind compilation isn’t some &lt;code class=&quot;language-text&quot;&gt;PostCSS&lt;/code&gt; magic, it
becomes clear that the path forward is just to add a bit of automation to the lifecycle of &lt;em&gt;manually&lt;/em&gt; compiling the css.
With this setup, I can change any css and see live reloads and I can even make changes to my &lt;code class=&quot;language-text&quot;&gt;tailwind.config.js&lt;/code&gt; and
have the changes live reload too (following a Tailwind compilation which takes a few seconds in dev) since ultimately,
you’re just including another css file in the &lt;em&gt;scss&lt;/em&gt; build pipeline which Angular knows quite well how to handle!&lt;/p&gt;
&lt;p&gt;I found the augmentation of Ionic with a sprinkle of Tailwind to be well worth the effort of figuring it out, and what’s
better is I got to nuke a large amount of Ionic css from my bundle and instead include a purgable chunk of only the
utilities I care about from Tailwind - give it a shot on your next Ionic Angular project :)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>12 min read</reading_time></item><item><title><![CDATA[Building a CLI for file path bookmarks]]></title><description><![CDATA[Tinkering around with oclif and building a little CLI that I published to npm]]></description><link>https://blog.andrewbrey.com/2020-03-30-building-a-cli-for-file-path-bookmarks/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2020-03-30-building-a-cli-for-file-path-bookmarks/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Mon, 30 Mar 2020 20:13:10 GMT</pubDate><content:encoded>&lt;p&gt;A while back I had been looking for a reason to play around with the &lt;a href=&quot;https://oclif.io/&quot;&gt;Open CLI Framework&lt;/a&gt; or &lt;code class=&quot;language-text&quot;&gt;oclif&lt;/code&gt;
for short - it’s a &lt;code class=&quot;language-text&quot;&gt;nodejs&lt;/code&gt; framework for building command line applications and it includes a lot of really nice things
out of the box like support for single and multi-command CLIs.&lt;/p&gt;
&lt;p&gt;When inspiration struck me in the form of a convenient method of “bookmarking” file paths in my terminal so that I can
quickly jump between projects and important directories I decided to give it a go.&lt;/p&gt;
&lt;p&gt;The result was an &lt;a href=&quot;https://github.com/andrewbrey/file-path-bookmarks&quot;&gt;open source&lt;/a&gt; &lt;code class=&quot;language-text&quot;&gt;npm&lt;/code&gt; package called
&lt;a href=&quot;https://www.npmjs.com/package/file-path-bookmarks&quot;&gt;file-path-bookmarks&lt;/a&gt; which lets you quickly and easily save
bookmarks in your terminal! This is a short one but pretty much just wanted to say “hey, I tried this thing and it was
pretty neat, I liked it, and I’ll probably be using it again in the future!” so, anyway…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>1 min read</reading_time></item><item><title><![CDATA[Building my blog with Gatsby, TypeScript, Tailwind, and Netlify CMS]]></title><description><![CDATA[How I built a GatsbyJS + Typescript + TailwindCSS + Netlify CMS blog website]]></description><link>https://blog.andrewbrey.com/2020-03-29-building-my-blog-with-gatsby-typescript-tailwind-and-netlify-cms/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2020-03-29-building-my-blog-with-gatsby-typescript-tailwind-and-netlify-cms/</guid><category><![CDATA[Projects]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Sun, 29 Mar 2020 00:00:44 GMT</pubDate><content:encoded>&lt;p&gt;For the last several weeks as I’ve been searching for a new job I have been surprised to find that not only is &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; a
whole lot hotter than &lt;code class=&quot;language-text&quot;&gt;Angular&lt;/code&gt; where I am in Seattle, but it’s honestly not even really close. I have spent the last
2.5 years on a weird little island &lt;a href=&quot;https://www.fluencyy.com&quot;&gt;doing consulting work&lt;/a&gt; where I could freely pick the tech
stack for client projects and I tended to err on the side of what I knew best (i.e. &lt;code class=&quot;language-text&quot;&gt;Angular&lt;/code&gt;) so that my time wasn’t
spent fiddling around with a unfamiliar technology.&lt;/p&gt;
&lt;p&gt;Now that I’ve come back from my blissfully unaware island, it really feels like my corner of the web development world
(i.e. the somewhere-between-a-startup-and-a-small-company world) has more or less left &lt;code class=&quot;language-text&quot;&gt;Angular&lt;/code&gt; in the past and is
significantly more focused on &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; and to a lesser extent &lt;code class=&quot;language-text&quot;&gt;Vue&lt;/code&gt;. I have only ever worked with these two technologies
a tiny bit and I felt like if I wanted to stay sharp on what is in demand, I should get a bit more experience with
&lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; under my belt - so I decided to indulge a long time &lt;em&gt;I should really build a blog&lt;/em&gt; feeling and…well, build a
blog.&lt;/p&gt;
&lt;p&gt;You are reading this on the blog that I built with &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;GatsbyJS&lt;/a&gt;, a framework for taking
components written in &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt;, filling them up with data sourced from a &lt;code class=&quot;language-text&quot;&gt;GraphQL&lt;/code&gt; layer that supports a really diverse
set of data sources, and spitting out pre-rendered “static” content that is much friendlier for SEO in the same way that
server rendered content is, and which can then be “re-hydrated” into a fully dynamic &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; application on the client -
I think it’s incredibly neat technology!&lt;/p&gt;
&lt;h2&gt;Project objectives&lt;/h2&gt;
&lt;p&gt;There were a few goals I had with this project (besides the previously stated “build a blog in React”):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Build it as an open source project which serves as a demo of how to build such a blog&lt;/p&gt;
&lt;p&gt;🎉 All source code for this blog can be found on my GitHub profile:
&lt;a href=&quot;https://github.com/andrewbrey/console.blog&quot;&gt;andrewbrey/console.blog&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Incorporate &lt;code class=&quot;language-text&quot;&gt;TailwindCSS&lt;/code&gt; (and possibly &lt;code class=&quot;language-text&quot;&gt;Tailwind UI&lt;/code&gt;) which has quickly become my favorite way to build and style web
content&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the &lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt; components in &lt;code class=&quot;language-text&quot;&gt;TypeScript&lt;/code&gt; instead of using &lt;code class=&quot;language-text&quot;&gt;PropTypes&lt;/code&gt; for type checking&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Incidentally, I read a post on the React Native blog this week where there was discussion of moving the code base to
using build-time type checking (presumably by moving to TypeScript?) rather than using run-time checking - does this
imply PropTypes enforce run-time type safety? I need to look into this more…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;Get it hooked up to &lt;code class=&quot;language-text&quot;&gt;Netlify&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Netlify CMS&lt;/code&gt; for builds, deploys, and content management&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Diving in to the details&lt;/h2&gt;
&lt;p&gt;As mentioned before, this whole project is &lt;a href=&quot;https://github.com/andrewbrey/console.blog&quot;&gt;open source&lt;/a&gt; and &lt;strong&gt;MIT&lt;/strong&gt;
licensed so feel free to skip this write up and dig in to the code if that’s more your thing, but if you’re interested
in my color-commentary on the experience of building, read on!&lt;/p&gt;
&lt;h3&gt;Overview&lt;/h3&gt;
&lt;p&gt;There are a few bullet points outside of the project objectives that I think are worthy of mention that I’ll present
here in a group:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All of the content for the blog is written in &lt;strong&gt;Markdown&lt;/strong&gt; as part of the git repository for the blog source code. The
&lt;code class=&quot;language-text&quot;&gt;.md&lt;/code&gt; files are transformed into HTML through a series of &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; plugins, collectively found in a file called
&lt;code class=&quot;language-text&quot;&gt;remark.plugin.ts&lt;/code&gt; which is part of the overall &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; framework configuration. As you might guess, the plugin is
called &lt;code class=&quot;language-text&quot;&gt;Remark&lt;/code&gt; and it has several accompanying plugins that do things like extract referenced images to the &lt;code class=&quot;language-text&quot;&gt;static&lt;/code&gt;
asset folder upon build, calculate the reading time for a post based on the number of words, and add code syntax
highlighting to &lt;em&gt;code fenced&lt;/em&gt; snippets.&lt;/li&gt;
&lt;li&gt;If you look at the markdown stuff just mentioned, you’ll see that I have all of the framework configuration in a
“non-standard” location (at least as far as I’ve seen on other &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; blogs), namely a &lt;code class=&quot;language-text&quot;&gt;config&lt;/code&gt; directory. The code
is also written in &lt;code class=&quot;language-text&quot;&gt;TypeScript&lt;/code&gt; and I leverage &lt;code class=&quot;language-text&quot;&gt;ts-node&lt;/code&gt; within the “normal” configuration files to allow me to import
&lt;code class=&quot;language-text&quot;&gt;.ts&lt;/code&gt; code into the typical &lt;code class=&quot;language-text&quot;&gt;.js&lt;/code&gt; configs.&lt;/li&gt;
&lt;li&gt;I’m using several other plugins for things like the &lt;a href=&quot;https://blog.andrewbrey.com/rss.xml&quot;&gt;rss feed&lt;/a&gt;, the
&lt;a href=&quot;https://blog.andrewbrey.com/sitemap.xml&quot;&gt;sitemap&lt;/a&gt;, and the Netlify redirect/header/caching rules - many of these
plugins require no manual configuration for my use case.&lt;/li&gt;
&lt;li&gt;You’ll notice that I have a &lt;code class=&quot;language-text&quot;&gt;.devcontainer&lt;/code&gt; directory that has some &lt;code class=&quot;language-text&quot;&gt;Docker&lt;/code&gt; related content inside. This is
configuration that allows me to run my local development environment inside of a container and attach my (container
host) instance of the &lt;strong&gt;Visual Studio Code&lt;/strong&gt; GUI to the running container instance. I get the isolation,
reproducibility, and portability of containers with the nice editor experience and tools of &lt;code class=&quot;language-text&quot;&gt;vscode&lt;/code&gt; - a very nice
combination! If you’re interested in learning more, check out the &lt;code class=&quot;language-text&quot;&gt;vscode&lt;/code&gt; article
&lt;a href=&quot;https://code.visualstudio.com/docs/remote/containers&quot;&gt;on developing inside containers&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Using TailwindCSS&lt;/h3&gt;
&lt;p&gt;There is a &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; plugin for &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-tailwindcss/&quot;&gt;TailwindCSS&lt;/a&gt;, but I opted
to instead add Tailwind myself using the “preferred” installation method discussed on the Tailwind
&lt;a href=&quot;https://tailwindcss.com/docs/installation/#using-tailwind-with-postcss&quot;&gt;documentation site&lt;/a&gt; which meant using
&lt;code class=&quot;language-text&quot;&gt;PostCSS&lt;/code&gt;. This worked better for me as I knew I was going to be adding other &lt;code class=&quot;language-text&quot;&gt;PostCSS&lt;/code&gt; plugins (namely &lt;code class=&quot;language-text&quot;&gt;PurgeCSS&lt;/code&gt; and
&lt;code class=&quot;language-text&quot;&gt;Autoprefixer&lt;/code&gt;) and I wanted to have as little &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; dependency as I could for this set of configurations. After
adding the &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-postcss/&quot;&gt;PostCSS&lt;/a&gt; plugin, I was off to the races.&lt;/p&gt;
&lt;h3&gt;Using TypeScript&lt;/h3&gt;
&lt;p&gt;Again, this was pretty straight forward - just install the
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-typescript/&quot;&gt;Gatsby TypeScript plugin&lt;/a&gt; and add a &lt;code class=&quot;language-text&quot;&gt;ts-config.json&lt;/code&gt; to
the root of the project! Don’t forget to also add &lt;code class=&quot;language-text&quot;&gt;ts-node&lt;/code&gt; as a &lt;strong&gt;devDependency&lt;/strong&gt; in your &lt;em&gt;package.json&lt;/em&gt; so that you
can make use of &lt;code class=&quot;language-text&quot;&gt;.ts&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;.tsx&lt;/code&gt; files in your &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; configurations.&lt;/p&gt;
&lt;h3&gt;Using Netlify and Netlify CMS&lt;/h3&gt;
&lt;p&gt;This was the goal that took the most fiddling to get working exactly right. In order to generate Netlify deployment
configuration files when you run &lt;code class=&quot;language-text&quot;&gt;gasby build&lt;/code&gt;, you need only add the
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-netlify/&quot;&gt;Netlify plugin&lt;/a&gt; with no extra configuration, but in order to
use &lt;a href=&quot;https://www.netlifycms.org/&quot;&gt;Netlify CMS&lt;/a&gt; (which is a &lt;strong&gt;git based&lt;/strong&gt; static site generator CMS) I ended up having to
take more manual control of the configuration than I originally wanted. To facilitate this, I grabbed the
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-netlify-cms/&quot;&gt;Netlify CMS plugin&lt;/a&gt;, which generates the &lt;strong&gt;admin&lt;/strong&gt; page
on your behalf, and &lt;em&gt;critically&lt;/em&gt;, has support for customizing the runtime CMS configuration within &lt;code class=&quot;language-text&quot;&gt;js&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;ts&lt;/code&gt; (which I
have done in &lt;code class=&quot;language-text&quot;&gt;src/cms/index.tsx&lt;/code&gt;). This allows me to run the CMS using a local git proxy server (also a Netlify
offering, see &lt;a href=&quot;https://www.netlifycms.org/docs/beta-features/&quot;&gt;the beta features documentation for details&lt;/a&gt;) when I’m in
my local environment, and using the &lt;em&gt;real&lt;/em&gt; Netlify git proxy API in production, all without maintaining local file
modifications that I have to remember to stash before committing!&lt;/p&gt;
&lt;p&gt;Another thing I customized in here was the blog post &lt;em&gt;preview template&lt;/em&gt; which allowed me to make the admin editing
experience produce a preview that looks just like (well, very close to) the final rendered site which is very helpful!&lt;/p&gt;
&lt;p&gt;That’s about it for the tour - there is more code in there but a lot of this an adaptation of the
&lt;a href=&quot;https://github.com/gatsbyjs/gatsby-starter-blog&quot;&gt;Gatsby stater blog template&lt;/a&gt; so it should be close to familiar to
anyone who has toured other &lt;code class=&quot;language-text&quot;&gt;Gatsby&lt;/code&gt; blog repositories - this just has a sprinkling of my personal preferences and
technology choices on top!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>7 min read</reading_time></item><item><title><![CDATA[Hello World!]]></title><description><![CDATA[Every good software project begins as a Hello World]]></description><link>https://blog.andrewbrey.com/2020-03-28-hello-world/</link><guid isPermaLink="false">https://blog.andrewbrey.com/2020-03-28-hello-world/</guid><category><![CDATA[General]]></category><dc:creator><![CDATA[Andrew Brey]]></dc:creator><pubDate>Sat, 28 Mar 2020 06:50:23 GMT</pubDate><content:encoded>&lt;p&gt;Here it is, the first post of &lt;code class=&quot;language-text&quot;&gt;console.blog&lt;/code&gt;! This is a bit of an experiment for me to learn about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;React&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;GatsbyJS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Netlify &amp;amp; Netlify CMS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;prefers-color-scheme&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Whether or not the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; element still works???&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and to have an excuse to use some shiny new components from &lt;a href=&quot;https://tailwindui.com/&quot;&gt;Tailwind UI&lt;/a&gt; which, as of this
writing, is in early access. I don’t know what this blog might become, and it is very &lt;em&gt;very&lt;/em&gt; possible that the eventual
answer is:&lt;/p&gt;
&lt;marquee&gt;
	&lt;span class=&quot;text-xl text-indigo-700 dark:text-fluencyy-200&quot;&gt;N o t h i n g&lt;/span&gt;
&lt;/marquee&gt;
&lt;blockquote&gt;
&lt;p&gt;Sweet, looks like there’s an answer for the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; question—&gt; &lt;strong&gt;YES!&lt;/strong&gt; (well, as of &lt;em&gt;right now&lt;/em&gt;, though the
animation is very janky on my phone).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anyway, &lt;strong&gt;Hello World!&lt;/strong&gt;&lt;/p&gt;</content:encoded><reading_time>1 min read</reading_time></item></channel></rss>