<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Brady Holt</title>
    <description>The latest articles on Forem by Brady Holt (@bradymholt).</description>
    <link>https://forem.com/bradymholt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F53564%2F6e8dca27-f571-4bf3-a4be-514873396acd.jpeg</url>
      <title>Forem: Brady Holt</title>
      <link>https://forem.com/bradymholt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bradymholt"/>
    <language>en</language>
    <item>
      <title>Database deadlocks</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 11 May 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/database-deadlocks-24an</link>
      <guid>https://forem.com/bradymholt/database-deadlocks-24an</guid>
      <description>&lt;p&gt;In a database system, a deadlock happens when two or more transactions wait for one another to release a lock. When this happens, the only course of action for the database engine is to kill one of the transaction to unblock the others.&lt;/p&gt;

&lt;p&gt;Here is a simple visualization of a deadlock:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lMJnDYng--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/database-deadlocks/deadlock.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lMJnDYng--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/database-deadlocks/deadlock.png" alt="Deadlock Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mitigations
&lt;/h3&gt;

&lt;p&gt;Here are some mitigation steps to avoid deadlocks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Take locks in a consistent order. Deadlocks happen when competing transactions take locks in opposite order. In the &lt;a href="https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS"&gt;PostgreSQL Explicit Locking documentation&lt;/a&gt;, the following deadlock example is given:&lt;/p&gt;

&lt;p&gt;Session 1:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Session 2:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;​
  UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Session 1:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  -- DEADLOCK WILL HAPPEN!​
  UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;​
  -- DEADLOCK HAPPENED!
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If Session 2 had updated the accounts record &lt;code&gt;WHERE account = 11111&lt;/code&gt; first and then &lt;code&gt;WHERE account = 22222&lt;/code&gt;, which is the same order as Session 1, a deadlock would be avoided.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find long running transactions and make them faster by optimizing or breaking them up into smaller transactions. The longer transactions run, the greater the chance of a deadlock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move statements that obtain a lock as far down in a transation as possible, so that the locks are obtained for a shorter duration of time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use explicit locking to take less aggressive locks where possible. For example, in SQL Server, evaluate using &lt;code&gt;READ UNCOMMITTED&lt;/code&gt; isolation level where possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Depending upon the database system, adding an index may help prevent deadlocks. For example, in SQL Server, a clustered index scan could be blocked by a row lock from another transaction. Adding another index could prevent this.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a last resort, you could add retry logic to the application. The application could catch a deadlock exception and then let the application gracefully retry until it succeeds. This option should be a last resort since it is working around the root cause of the problem.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Postgres composite types for tables</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Fri, 08 May 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/postgres-composite-types-for-tables-3cn7</link>
      <guid>https://forem.com/bradymholt/postgres-composite-types-for-tables-3cn7</guid>
      <description>&lt;p&gt;A nice feature in PostgreSQL is that when you create a table, “a composite type is also automatically created, with the same name as the table, to represent the table’s row type” (quoted from the &lt;a href="https://www.postgresql.org/docs/11/rowtypes.html"&gt;documentation&lt;/a&gt;). A composite type represents the structure of a row or record. This means you can work with a single record of a table much like an object in an OOP language.&lt;/p&gt;

&lt;p&gt;So, if you create an &lt;code&gt;example&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE example (​
  id int,​
  name text​
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You also get a composite type (also known as a “user defined type” in other database systems) named &lt;code&gt;example&lt;/code&gt;.Then, in PL/pgSQL, you can then do things like:&lt;/p&gt;

&lt;p&gt;Create a function that returns an &lt;code&gt;example&lt;/code&gt; record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE OR REPLACE FUNCTION construct_example (p_name text)​
RETURNS example​
AS $$​
DECLARE​
  v_example example;​
BEGIN​
  v_example.id = 1;​
  v_example.name = p_name;​
  RETURN v_example;​
END;​
$$​
LANGUAGE plpgsql;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Use an &lt;code&gt;example&lt;/code&gt; record as a single record source for an &lt;code&gt;INSERT INTO&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO example VALUES ((SELECT construct_example('ABC')).*);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;ROW&lt;/code&gt; to construct a record on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ROW(1::int, 'ABC'::text)::example;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Use JSON to hydrate a record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v_json_object = '{"id": 1, "name": "ABC"}'::json;​
v_example = json_populate_record(NULL::example, v_json_object);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It’s actually quite handy to be able to work with a single record that is of the same type as a table.&lt;/p&gt;

&lt;h3&gt;
  
  
  IS NULL and IS NOT NULL
&lt;/h3&gt;

&lt;p&gt;One curious thing about composite types is how the &lt;code&gt;IS NULL&lt;/code&gt; and &lt;code&gt;IS NOT NULL&lt;/code&gt; constructs work on them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IS NULL&lt;/code&gt; is &lt;code&gt;TRUE&lt;/code&gt; if a variable of the composite type is &lt;code&gt;NULL&lt;/code&gt; or if &lt;strong&gt;all&lt;/strong&gt; the fields of the record are &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, both of these evaluate to &lt;code&gt;TRUE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ROW(NULL::int, NULL::text)::example IS NULL;​
SELECT NULL::example IS NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IS NOT NULL&lt;/code&gt;, on the other hand, is only &lt;code&gt;TRUE&lt;/code&gt; if &lt;strong&gt;all&lt;/strong&gt; the fields in the record are not NULL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ROW(1::int, NULL::text)::example IS NOT NULL;​
-- false!​
SELECT ROW(1::int, 'ABC'::text)::example IS NOT NULL;​
-- true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Because of this, a composite type variable can have both &lt;code&gt;IS NOT NULL&lt;/code&gt; and &lt;code&gt;IS NULL&lt;/code&gt; constructs equal to &lt;code&gt;FALSE&lt;/code&gt; at the same time!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Teaching Kids to Code</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Tue, 28 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/teaching-kids-to-code-143j</link>
      <guid>https://forem.com/bradymholt/teaching-kids-to-code-143j</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--njhsZ7sE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/teaching-kids-to-code/teasure-castle-win.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--njhsZ7sE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/teaching-kids-to-code/teasure-castle-win.png" alt="COVID-19 Chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few months ago, I decided to build an app with my 9 year old daughter and her friend Noah. They seemed interested and I was excited at the prospect of inspiring them to learn more about programming.&lt;/p&gt;

&lt;p&gt;We setup a time and about an hour before I figured I better come up with a plan. When I starting thinking about it, I had no idea where to start!&lt;/p&gt;

&lt;p&gt;Questions on my mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What language should I use?&lt;/li&gt;
&lt;li&gt;What type of app should be build? Game? Terminal app? Website?&lt;/li&gt;
&lt;li&gt;How do I teach them? Do I start with basics and then build up? Or, should we just build something and fill in the gaps later?&lt;/li&gt;
&lt;li&gt;No, really, do I teach these 9 year olds what an array is or just start typing and tell them what I’m doing?&lt;/li&gt;
&lt;li&gt;What if I go too fast? Will I know? Will they tell me? Or, will they just get lost and feel like they aren’t understanding and give up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I was surprised how hard it seemed to simply teach something to them. It was particularly challenging because they were starting from scratch. When I’ve given talks at meetups and conferences, my audience has been professional developers. Bridging the gap between their experience and what I’m teaching them is mucheasier than bridging the gap between being a professional developer and a 9 year old that knows nothing about computer programming.&lt;/p&gt;

&lt;p&gt;I still don’t know the answer to most of those questions, admittedly. But, &lt;strong&gt;I do have a newfound respect for teachers&lt;/strong&gt;. Especially teachers teaching elementary topics.&lt;/p&gt;

&lt;p&gt;I decided that I wanted to do something to keep them engaged. I knew I would lose them if I started talking about variables and data structures right away. So,I just asked them what they wanted to build. “A game”. Of course, right?&lt;/p&gt;

&lt;p&gt;So, we talked about the details of the game. What would be the objective? How will you win? How does the game play work and what are the rules? Their wheels were spinning and I had to rein them in a bit because this little game app was getting complicated fast.&lt;/p&gt;

&lt;p&gt;We eventually came up with a simple plan: The name of the game is &lt;strong&gt;Tresure Castle&lt;/strong&gt;. You are asked a series of questions and are given keys as you answer questions correctly. Once you get enough keys, you can unlock the castle and you win the game.&lt;/p&gt;

&lt;p&gt;I opened up VSCode, created an &lt;code&gt;index.js&lt;/code&gt; file and start coding in JavaScript. I told them what I was doing along the way but didn’t get too detailed. I figured they would ask if they really wanted to know. I stopped along the way and ask some questions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Noah - see this variable that tells me how many keys I have and this other variable that defines how many keys you need to win? How do you think I can find out how many more keys they need. [Subtract them?] Yes! Now, we can subtract them and store the result in &lt;em&gt;another&lt;/em&gt; variable. Then, we can use that.&lt;/p&gt;

&lt;p&gt;Kayla - See how we’re looping through the questions we ask the user? What do you think we should do once they get enough keys? Should we keep looping? [No?] Right, we need to stop and tell them they’ve won! Here’s how you do that…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Honestly, I felt like I didn’t know what I was doing but it worked out fine. We just tried to have fun and I tried to adjust as we went. When they got lost, I slowed down. When I felt things were clicking I sped up a bit. I tried to make it fun and engaging to keep their interest. All along the way, I kept focusing on &lt;strong&gt;my main goal: to enspire them&lt;/strong&gt;. I really hoped they would clearly make the connection that we are telling the computer what to do and it does it. And, we could tell it to do anything! That’s amazing to me and I hoped they would see that too.&lt;/p&gt;

&lt;p&gt;Time will tell if they do :)&lt;/p&gt;

&lt;p&gt;Here’s the code repository for our humble little game: &lt;a href="https://github.com/bradymholt/treasure-castle"&gt;https://github.com/bradymholt/treasure-castle&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How we use OpenAPI / Swagger for the YNAB API</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 16 Mar 2020 21:27:46 +0000</pubDate>
      <link>https://forem.com/ynab/how-we-use-openapi-swagger-for-the-ynab-api-5453</link>
      <guid>https://forem.com/ynab/how-we-use-openapi-swagger-for-the-ynab-api-5453</guid>
      <description>&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OpenAPI Specification&lt;/th&gt;
&lt;th&gt;➡&lt;/th&gt;
&lt;th&gt;✅ Tests&lt;br&gt;✅ Documentation&lt;br&gt;✅ Client Libraries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.youneedabudget.com" rel="noopener noreferrer"&gt;YNAB&lt;/a&gt; combines software with 4 simple rules to help our users gain control their money.  Back in 2018 we released an API to help our community build things to connect their budget to other apps and services.&lt;/p&gt;

&lt;p&gt;We built the &lt;a href="https://api.youneedabudget.com/" rel="noopener noreferrer"&gt;YNAB API&lt;/a&gt; using the &lt;a href="https://swagger.io/docs/specification/about/" rel="noopener noreferrer"&gt;OpenAPI Specification&lt;/a&gt; and &lt;a href="https://swagger.io/" rel="noopener noreferrer"&gt;Swagger tooling&lt;/a&gt;.  In retrospect we think it was a good decision and has provided many benefits.  This post gives a landscape of our setup and how the tooling works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The specification file
&lt;/h2&gt;

&lt;p&gt;Everything starts with our &lt;a href="https://swagger.io/docs/specification/2-0/basic-structure/" rel="noopener noreferrer"&gt;OpenAPI Specification&lt;/a&gt; file: &lt;a href="https://api.youneedabudget.com/papi/spec-v1-swagger.json" rel="noopener noreferrer"&gt;https://api.youneedabudget.com/papi/spec-v1-swagger.json&lt;/a&gt;.  This is a fairly simple JSON file that we edit when anything changes on the API. &lt;br&gt;
 This file defines the endpoints and the shape of request and response data.&lt;/p&gt;

&lt;p&gt;With this spec file in place, we get access to some great tooling.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;We use a Ruby gem called &lt;a href="https://github.com/westfieldlabs/apivore" rel="noopener noreferrer"&gt;Apivore&lt;/a&gt; to test our actual API implementation against our OpenAPI spec.  When our tests run, they exercise all the spec defined endpoints and ensure they are present and work as expected.  With Apivore, you have a &lt;code&gt;validate_all_paths&lt;/code&gt; method, which does a simple validation to ensure endpoints are available and return the expected statuses.  But, you can also exercise the API by passing certain data and Apivore will ensure the shape of the data responses match your spec.  So, if your spec says an endpoint returns an array of sharks: &lt;code&gt;[{"id": 1, "name": "megamouth"},{"id": 2, "name": "hammerhead"}]&lt;/code&gt; but a particular request returns a single shark: &lt;code&gt;{"id": 1, "name": "megamouth"}&lt;/code&gt; a test will fail.  Another example: If you were to change the name of a field in the JSON response, a test would fail.&lt;/p&gt;
&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;p&gt;Here, one of our tests ensures that requesting a non-existent budget at &lt;code&gt;/budgets/:id&lt;/code&gt; returns a 404.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'response with 404 for a non-existent budget'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;:get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/budgets/{budget_id}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="s1"&gt;'budget_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we ensure the &lt;code&gt;/budgets/:id/payees/:id&lt;/code&gt; endpoint returns a payee, when requested, and also that the shape of that payee conforms to our spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'returns a single payee'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;:get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/budgets/{budget_id}/payees/{payee_id}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ability to use a tool like Apivore against our spec is a huge win because we get automatic testing.  Having this alone would be a case for using an OpenAPI spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Using &lt;a href="https://swagger.io/tools/swagger-ui/" rel="noopener noreferrer"&gt;Swagger UI&lt;/a&gt; we are able to automatically generate our &lt;a href="https://api.youneedabudget.com/v1" rel="noopener noreferrer"&gt;documentation page&lt;/a&gt; just by pointing to our spec.  We did make some customizations to suit our preferences but any changes to our spec file are automatically reflected on that page.&lt;/p&gt;

&lt;p&gt;For example, the documentation for &lt;code&gt;GET /budgets/:id&lt;/code&gt; shows the query parameters that can be passed, expected response status code, and the shape of the response data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp4iu8a4n4uev3pvixdro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp4iu8a4n4uev3pvixdro.png" alt="Endpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Swagger UI also has the ability to actually use the API on the page itself which is a great help for developers wanting to test something or quickly see the API responses.&lt;/p&gt;

&lt;p&gt;For example, this is what the page looks like when requesting a specific payee:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqkkdpwz1fpio84566lx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqkkdpwz1fpio84566lx2.png" alt="Endpoint Try-it Now"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Client Libraries
&lt;/h2&gt;

&lt;p&gt;Arguably, Swagger tooling is most known for &lt;a href="https://swagger.io/tools/swagger-codegen/" rel="noopener noreferrer"&gt;Swagger Codegen&lt;/a&gt;, which generates client libraries for interfacing with an API.&lt;/p&gt;

&lt;p&gt;We use Codegen to generate our &lt;a href="https://github.com/ynab/ynab-sdk-js" rel="noopener noreferrer"&gt;JavaScript client&lt;/a&gt; and our &lt;a href="https://github.com/ynab/ynab-sdk-ruby" rel="noopener noreferrer"&gt;Ruby client&lt;/a&gt;.  And others in our community use it to build &lt;a href="https://api.youneedabudget.com/#clients-community" rel="noopener noreferrer"&gt;clients for other languages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the nice things about Codegen is the ability to customize the generated client with the use of templates.  For example, we &lt;a href="https://github.com/ynab/ynab-sdk-js/tree/master/swagger-templates" rel="noopener noreferrer"&gt;specify a number of templates&lt;/a&gt; to override the defaults on our JavaScript client.  This allows us to customize things to our suit our preferences.  &lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript Definitions
&lt;/h3&gt;

&lt;p&gt;Our JavaScript client uses the &lt;code&gt;typescript-fetch&lt;/code&gt; generator which, along with generating a JavaScript client usable from both Node.js and the browser, generates &lt;a href="https://github.com/ynab/ynab-sdk-js/blob/master/dist/index.d.ts" rel="noopener noreferrer"&gt;TypeScript definition files&lt;/a&gt;.  This is really useful because developers who are using TypeScript tooling (like &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;) can get develop-time support.&lt;/p&gt;

&lt;h4&gt;
  
  
  Examples
&lt;/h4&gt;

&lt;p&gt;IntelliSense support so a developer can clearly see the available fields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk9cu9sq08s5n8sj6s2b7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk9cu9sq08s5n8sj6s2b7.png" alt="IntelliSense"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Develop-time errors (a.k.a. red squiggles) so a developer can see when they have accessed a field that does not exist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fps1palq1e1pfn1o5kfug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fps1palq1e1pfn1o5kfug.png" alt="Red squiggles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enums selection so a developer can easily select from a list of supported values:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgfts2tp0rzzllyq2krmx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgfts2tp0rzzllyq2krmx.png" alt="Enums"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All of this support is coming directly from the original OpenAPI specification file!&lt;/p&gt;

&lt;h3&gt;
  
  
  Overall Experience
&lt;/h3&gt;

&lt;p&gt;We've been pleased with our usage of an OpenAPI specification and Swagger tooling to build out our API and the ecosystem around it.  Of course, there have been a few bumps along the way and we still would like to tweak some things but the amount of benefit this tooling brings is significant and allows us to ship things more rapidly.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Playing with MediaStream API</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Wed, 26 Feb 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/playing-with-mediastream-api-5ghh</link>
      <guid>https://forem.com/bradymholt/playing-with-mediastream-api-5ghh</guid>
      <description>&lt;p&gt;I recently purchased a new headset for use on video conference calls at work. I wanted to test out the microphone to see how it sounded compared to my internal MacBook microphone. I started fiddling around with using macOS Voice Memos and it worked but it wasn’t long until I was looking on the web for “test microphone” site.  Sadly, the sites I found were full of ads and bloated with things I didn’t want.&lt;/p&gt;

&lt;p&gt;Since I’m a developer and can’t help myself, I set out to build something to my own liking. It was time to play with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API"&gt;MediaStream API&lt;/a&gt;, something I really haven’t developed for before.&lt;/p&gt;

&lt;p&gt;The way I like to learn something new is to get the big picture first. Then, dive into the details. Here in this case, this meant getting a super simple script working that would start recording when the page is opened, stop after 5 seconds, and automatically play it back. It ended up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This script starts recording when the page is loaded, stops after 5 seconds​
// and then automatically plays back what was recorded.​
navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) {​
  let chunks = [];​
  const mediaRecorder = new MediaRecorder(stream);​
​
  mediaRecorder.ondataavailable = function(e) {​
    // Store data stream chunks for future playback​
    chunks.push(e.data);​
  };​
​
  mediaRecorder.onstop = function(e) {​
    // Playback recording​
    const blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" });​
    const audio = document.createElement("audio");​
    audio.src = window.URL.createObjectURL(blob);​
    document.body.appendChild(audio);​
    audio.play();​
​
    // Clear recording​
    chunks = [];​
  };​
​
  // Start recording!​
  mediaRecorder.start();​
​
  // Record for 5 seconds then stop and playback​
  setTimeout(()=&amp;gt;{​
    mediaRecorder.stop();​
  }, 5000);​
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you drop the above code in a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag you have a working recorder / playback. Easy and simple!&lt;/p&gt;

&lt;p&gt;For the record, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API/Using_the_MediaStream_Recording_API"&gt;this MDN article&lt;/a&gt; and corresponding &lt;a href="https://mdn.github.io/web-dictaphone/"&gt;demo&lt;/a&gt; were really helpful in understanding how the MediaStream API works.&lt;/p&gt;

&lt;p&gt;Once I had that working, I iterated on the details and eventually landed with something I like and find useful: an app where you click ‘Record’, make some sound, click ‘Stop’ and then it automaitcally plays it back. Simple, right? Also, it shows a nice little realtime graph of the audio input.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DC0CMO3S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.geekytidbits.com/playing-with-mediastream-api/test-microphone-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DC0CMO3S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://www.geekytidbits.com/playing-with-mediastream-api/test-microphone-demo.gif" alt="Test Microphone Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the working app here: &lt;a href="https://bradymholt.github.io/test-microphone/"&gt;https://bradymholt.github.io/test-microphone/&lt;/a&gt; and view the source here: &lt;a href="https://github.com/bradymholt/test-microphone"&gt;https://github.com/bradymholt/test-microphone&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>YNAB at KotlinConf 2019</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 06 Jan 2020 14:38:07 +0000</pubDate>
      <link>https://forem.com/ynab/ynab-at-kotlinconf-2019-63k</link>
      <guid>https://forem.com/ynab/ynab-at-kotlinconf-2019-63k</guid>
      <description>&lt;p&gt;A few weeks ago some of the YNAB team attended &lt;a href="https://kotlinconf.com/"&gt;KotlinConf 2019&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We have been using Kotlin in our Android app for some time and are particularly interested in its &lt;a href="https://kotlinlang.org/docs/reference/multiplatform.html"&gt;multiplatform  capability&lt;/a&gt; as a way to share code across Android, iOS and the Web.&lt;/p&gt;

&lt;p&gt;After attending quite a few multiplatform focused sessions, chatting with folks at the conference, and discussing things as a team, we're planning to start experimenting with multiplatform usage across Android and iOS and then eventually on the Web.  There is significant potential here that we want to explore!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RR8Z00XE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bodic5e3wio5quifnb12.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RR8Z00XE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bodic5e3wio5quifnb12.jpg" alt="YNAB at KotlinConf 2019"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Left to Right: Kevin, Graham, Taylor, Jeff, Brady&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Updating My ASP.NET Core Template to .NET Core 3.1</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 16 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/updating-my-asp-net-core-template-to-net-core-3-1-2pdf</link>
      <guid>https://forem.com/bradymholt/updating-my-asp-net-core-template-to-net-core-3-1-2pdf</guid>
      <description>&lt;p&gt;Back in 2016, I decided to build an ASP.NET Core template app so I could play with the framework and get a feel for working with .NET Core. It turned out to be a fun process but took a long time! Admittedly, I took it further than I initially planned to, by adding email functionality, scaffolding tests, building in database migrations, provisioning / deployment with Ansible, and &lt;a href="https://github.com/bradymholt/aspnet-core-react-template#overview-of-stack"&gt;much more&lt;/a&gt;. I learned a lot.&lt;/p&gt;

&lt;p&gt;It become helpful to some others, to my joy. I got some contributions, questions, and some link backs to my repository as a reference. That type of interest and involvement in the open-source community is really what makes it fun and gives me encouragement to keep contributing.&lt;/p&gt;

&lt;p&gt;Back in September, &lt;a href="https://github.com/bradymholt/aspnet-core-react-template/issues/43"&gt;Victor Cardins asked&lt;/a&gt; if I planned to upgrade the template to .NET 3.0. It had been awhile since I worked with this project and the idea seemed to be too much to tackle with all the other things I was juggling at the time. But I had some spare time last week, and a burst of interest, so I decided to take the plunge. It took longer than I thought it would (of course) but I &lt;a href="https://github.com/bradymholt/aspnet-core-react-template/pull/46"&gt;finally finished it&lt;/a&gt;. This upgrade was certainly rougher than than the upgrade from &lt;a href="https://github.com/bradymholt/aspnet-core-react-template/pull/36"&gt;2.0. to 2.1&lt;/a&gt; as quite a few foundational APIs have changed. By far the biggest change was the &lt;a href="https://github.com/aspnet/AspNetCore/issues/12890"&gt;deprecation of Microsoft.AspNetCore.SpaServices&lt;/a&gt; because I had to restructure how Webpack was integrated for on-demand bundling and HMR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository Link
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bradymholt/aspnet-core-react-template"&gt;https://github.com/bradymholt/aspnet-core-react-template&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>KotlinConf 2019</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Sat, 07 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/kotlinconf-2019-5h6m</link>
      <guid>https://forem.com/bradymholt/kotlinconf-2019-5h6m</guid>
      <description>&lt;p&gt;This week I had the opportunity to attend &lt;a href="https://kotlinconf.com/"&gt;KotlinConf 2019 in Copenhagen, Denmark&lt;/a&gt; with 4 other fellow colleagues at YNAB. It was a great time of learning new things, meeting new people, seeing the city of Copenhagen, and spending time with my fellow coworkers and friends.&lt;/p&gt;

&lt;p&gt;Kotlin is not a language I use (yet) but at YNAB we are evaluating using its &lt;a href="https://kotlinlang.org/docs/reference/multiplatform.html"&gt;multiplatform capability&lt;/a&gt; to enable writing code in Kotlin that can be compiled down to JVM (for Andriod), Native (for iOS), and JavaScript (for Web). The prospect of us being able to share code in this way is compelling so we want to learn more.&lt;/p&gt;

&lt;p&gt;I walked away admiring the Kotlin language, impressed with its community, and hopeful for the future in regards to multiplatform support and features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions I Attended
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Day 1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go multiplatform with Kotlin Workshop - Salomon BRYS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Day 2
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Building Progressive Web Apps in Kotlin - Erik Hellman&lt;/li&gt;
&lt;li&gt;MPP in 1.3.X and beyond - Dmitry Savvinov &amp;amp; Liliia Abdulina&lt;/li&gt;
&lt;li&gt;What the F(p) is Kotlin? Shelby Cohen &amp;amp; Katie Levy&lt;/li&gt;
&lt;li&gt;Bridge the physical world: Kotlin/Native on Raspberry Pi - Qian Jin&lt;/li&gt;
&lt;li&gt;Your Multiplatform Kaptain has Arrived - Ahmed El-Helw&lt;/li&gt;
&lt;li&gt;Shipping a Mobile Multiplatform Project on iOS &amp;amp; Android - Ben Asher &amp;amp; Alec Strong&lt;/li&gt;
&lt;li&gt;Kotlin Native Concurrency Explained - Kevin Galligan&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Day 3
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Asynchronous Data Streams with Kotlin Flow - Roman Elizarov&lt;/li&gt;
&lt;li&gt;Kotlin Uncovered - Chet Haase &amp;amp; Romain Guy&lt;/li&gt;
&lt;li&gt;What’s new in Java 19: The end of Kotlin? - Jake Wharton&lt;/li&gt;
&lt;li&gt;Kotlin in Space - Maxim Mazin&lt;/li&gt;
&lt;li&gt;Do it in code (not YAML)! Unlock power of Kotlin DSL for Kubernetes - Fedor Korotkov&lt;/li&gt;
&lt;li&gt;Kotlin puzzlers, vol 3 - Anton Keks&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Communicating Visually While Working Remotely</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 25 Nov 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/communicating-visually-while-working-remotely-5c8d</link>
      <guid>https://forem.com/bradymholt/communicating-visually-while-working-remotely-5c8d</guid>
      <description>&lt;p&gt;One thing I have learned in since I started working remotely about five years ago is that communicating clearly is very important. Remote working has many benefits but inherently better communication is not one of them.&lt;/p&gt;

&lt;p&gt;Context can be easily lost. The other person may be in a different timezone. Cultural jargon you understand can be lost with other cultures. There is a time delay when using asynchronous means of communicating (chat, comments).&lt;/p&gt;

&lt;p&gt;There are things I’ve learned to help overcome these challenges. Communicating clearly, with concrete examples is one of them. Making sure to “spell things out” and not assume my reader will know insider jargon or acronyms. But, one of my favorite things to do is communicate with screenshots, annotations, animated GIFs and videos &lt;strong&gt;often&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’ve spent quite awhile finding tools I like and setting things up to make me productive. I’d like to share my setup in hopes it spurs some ideas in others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://support.apple.com/en-us/HT201361"&gt;macOS screenshots&lt;/a&gt; - The built in tool to “Copy picture of selected area to the clipboard” (mapped to Command + Control + Shift + 4 by default) is easy, fast, and a good tool for quickly selecting an area of my screen. I use it when I don’t need to annotate and I can copy/paste from clipboard to my target app. &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--86LJGCGb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/my-remote-communication-tools/macos-screenshot.png" alt="macOS Screenshot"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://monosnap.com/"&gt;Monosnap&lt;/a&gt; - When adding some text, drawing a box, or pointing with an arrow will help communicate something more clearly, I use Monosnap. Also, I like using Monosnap for making videos since it supports easy sharing. &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IF_XgX3x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/my-remote-communication-tools/monosnap.png" alt="Monosnap"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gifox.io/"&gt;Gifox&lt;/a&gt; - This is a simple tool for record animated GIFs and has drag/drop support which makes it suitable for quick sharing on GitHub and Slack. &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x3pkEsa6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/my-remote-communication-tools/gifox.png" alt="Gifox"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keyboard Shortcuts
&lt;/h2&gt;

&lt;p&gt;I have keyboard shortcuts setup for these tools, in a progressive order. This makes using this quick and easy to remember.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screenshot: Command + Shift + 1 (macOS Copy picture of selected area to the clipboard)&lt;/li&gt;
&lt;li&gt;Annotated Screenshot: Command + Shift + 2 (Monosnap)&lt;/li&gt;
&lt;li&gt;Animated GIF: Command + Shift + 3 (Gifox)&lt;/li&gt;
&lt;li&gt;Video: Command + Shift + 4 (Monosnap)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jfK8yfoW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/my-remote-communication-tools/keyboard-shortcuts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jfK8yfoW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.geekytidbits.com/my-remote-communication-tools/keyboard-shortcuts.png" alt="Keyboard Shortcuts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing
&lt;/h2&gt;

&lt;p&gt;If the app I am sharing to supports copy/paste or drag/drop I will use that. For example, I do this when sharing screenshots and animated GIFs to Slack or GitHub. This allows my audience to see the visual quickly without having to click a link.&lt;/p&gt;

&lt;p&gt;For all other cases (including email), I will upload the media to Google Drive and share the link. Monosnap and Gifox both support sharing to Google Drive.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>GitHub Actions Version and Release to npm</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 11 Nov 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/bradymholt/github-actions-version-and-release-to-npm-ppg</link>
      <guid>https://forem.com/bradymholt/github-actions-version-and-release-to-npm-ppg</guid>
      <description>&lt;p&gt;I have been playing with &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt; quite a bit recently at YNAB and for some personal projects and it has been fun. Most recently I got &lt;a href="https://github.com/bradymholt/xertz"&gt;xertz&lt;/a&gt;, my static site generator project, running on GitHub Actions for build/test and releases to the npm registry.&lt;/p&gt;

&lt;p&gt;I wanted to walk through the workflow I wrote to release a new version to the npm registry. With this workflow, when I merge a PR, a new release will be tagged and pushed up to npm. This is one less thing I have to do manually when I want to get a change out to a package I manage. Automated scripts for deploying to npm are certainly not a new things but getting this working on GitHub Actions was a new thing for me.&lt;/p&gt;

&lt;p&gt;The full workflow script is &lt;a href="https://github.com/bradymholt/xertz/blob/master/.github/workflows/publish.yml"&gt;in the xertz repository&lt;/a&gt; so if you just want to take a quick look, please do.&lt;/p&gt;

&lt;p&gt;Below, I will work through the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walkthrough
&lt;/h2&gt;

&lt;p&gt;The first bit is the trigger clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Publish to npm Registry​
​
on:​
  pull_request:​
    types: closed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When should GitHub Actions run this workflow? Any time a Pull Request is closed. But, I actually only want it to run when a PR is &lt;em&gt;merged&lt;/em&gt; and I want it to run on the master branch. Those additional filters will come later.&lt;/p&gt;

&lt;p&gt;Now, the next bit simply names the first (and only) job in the workflow “publish” and tells GitHub Actions to run it on the latest supported version of Ubuntu. This is pretty standard stuff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:​
  publish:​
    runs-on: ubuntu-latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we get to the main part of the workflow. The steps. These are the steps the “publish” job will run, in sequential order. Above, I told this workflow to trigger on pull requests being closed. Here, I will tell it to only trigger if a pull request is closed &lt;em&gt;because&lt;/em&gt; it was merged (&lt;code&gt;if: github.event.pull_request.merged&lt;/code&gt;). Also, I’ll specify that I want the workflow to checkout and use the &lt;em&gt;master&lt;/em&gt; branch (&lt;code&gt;refs/heads/master&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps:​
  - uses: actions/checkout@v1​
    if: github.event.pull_request.merged​
    with:​
      ref: refs/heads/master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So far, my workflow will run using the latest commit on the master branch, after a Pull Request is merged. Next, I’ll specify I want it to use Node.js version 8 and to run &lt;code&gt;npm install&lt;/code&gt; to make sure all the dependent packages are installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Install Node.js​
  uses: actions/setup-node@v1​
  with:​
    node-version: 8​
- name: npm install​
  run: npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, I’m going to setup some git config (user.name and user.email) in preparation of pushing new git tags up to GitHub. &lt;code&gt;$GITHUB_ACTOR&lt;/code&gt; is an automatically available environment variable and is the “the name of the person or app that initiated the workflow”, which in this case will be my down GitHub username (bradymholt) since I will be the one merging Pull Requests which triggers the workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: version and publish​
  run: |​
    git config user.name $GITHUB_ACTOR​
    git config user.email gh-actions-${GITHUB_ACTOR}@github.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, I’ll login to npm through the CLI. &lt;code&gt;$NPM_API_TOKEN&lt;/code&gt; is a token I generated on my npm account and stored as a GitHub &lt;a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#creating-and-using-secrets-encrypted-variables"&gt;Secret&lt;/a&gt; on the repository, and then exposed as an environment variable (see below).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm config set //registry.npmjs.org/:_authToken=$NPM_API_TOKEN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ok, now I’ll do some standard npm commands to version and publish my package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm version minor --force -m "Version %s"​
npm publish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;By default, when you use &lt;code&gt;npm version&lt;/code&gt; (as I did above), it will create a git version commit and tag. I want to push both of these back up to GitHub to keep track of them. So, here, I will add the GitHub remote and push to it. &lt;code&gt;$GITHUB_TOKEN&lt;/code&gt; is a built-in secret, exposed as an environment variable (see below), with access to the repository. &lt;code&gt;$GITHUB_REPOSITORY&lt;/code&gt; is also a built-in environment variable referencing the, well, repository name which in this case is “bradymholt/xertz”.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote add gh-origin https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git​
git push gh-origin HEAD:master --tags
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, the &lt;code&gt;env:&lt;/code&gt; section specifies environment variables to be made available for the duration of the workflow. Above, I referenced &lt;code&gt;$NPM_API_TOKEN&lt;/code&gt; (manually added secret) and &lt;code&gt;$GITHUB_TOKEN&lt;/code&gt; (automatically added secret). You have to explicitly make secrets available as environment variables by using the following syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env:​
  NPM_API_TOKEN: ${{ secrets.NPM_API_TOKEN }}​
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Only then can you use them in the steps. Do note that other automatically added environment variables that are not considered secrets, such as &lt;code&gt;$GITHUB_REPOSITORY&lt;/code&gt;, do not require this explicit syntax.&lt;/p&gt;

&lt;p&gt;That’s it. I hope this proves useful to others.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Progressive Conversion of TypeScript Namespaces to Modules</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Mon, 16 Sep 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/ynab/progressive-conversion-of-typescript-namespaces-to-modules-3f1j</link>
      <guid>https://forem.com/ynab/progressive-conversion-of-typescript-namespaces-to-modules-3f1j</guid>
      <description>&lt;p&gt;We love TypeScript at &lt;a href="https://youneedabudget.com"&gt;YNAB&lt;/a&gt;. One of our main modules is something we call the “Shared Library” and it is a quite large TypeScript project. Actually, it’s comprised of 3 library projects and 3 test projects. It’s big. And, it was initially written using TypeScript namespaces, before TypeScript had support for ES modules.&lt;/p&gt;

&lt;p&gt;We wanted to start converting this library over to using ES modules for the various benefits that gives including the ability to tree-shake and better development tooling. But, it became obvious we needed to find a &lt;em&gt;progressive&lt;/em&gt; way to do this because a few attempts at an all-or-nothing approach proved daunting. Thousands of errors and no simple or obvious way to automate the conversion.&lt;/p&gt;

&lt;p&gt;Guidance for progressively converting a project from namespaces to modules is slim. There is &lt;a href="https://github.com/Microsoft/TypeScript/issues/12473"&gt;this GitHub issue&lt;/a&gt; where some discuss approaches but there are still some gaps the approaches.&lt;/p&gt;

&lt;p&gt;To setup progressive migration, we ended up doing the following which has been working well for us.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;"exclude": ["**/*.m.ts"]&lt;/code&gt; to the tsconfig.json file in the original global / namespace project. This allows you to create modules with .m.ts extention and keep the &lt;code&gt;outFile&lt;/code&gt; config.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;tsconfig.module.json&lt;/code&gt; file adjacent to the main tsconfig.json file.&lt;/li&gt;
&lt;li&gt;Use a &lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html"&gt;project reference&lt;/a&gt; in the tsconfig.module.json file pointing to the namespace project: &lt;code&gt;"references": [{ "path": "./tsconfig.json" }]&lt;/code&gt;. This makes the global / namespace &lt;em&gt;types&lt;/em&gt; available from within the modules project. It doesn’t emit the code for this project but it tells TypeScript to assume these types will be available at runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It looks like this:&lt;/p&gt;

&lt;h4&gt;
  
  
  tsconfig.json
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../dist/package.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"composite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"**/*.m.ts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  tsconfig.module.json
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esnext"&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./MyClass.m.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./tsconfig.json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With the above setup, you can create module files with the extension &lt;code&gt;.m.ts&lt;/code&gt; adjacent to the existing namespace files like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;- MyClass.ts​
- MyClass.m.ts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then, as &lt;a class="comment-mentioned-user" href="https://dev.to/danielrosenwasser"&gt;@danielrosenwasser&lt;/a&gt;
 &lt;a href="https://github.com/Microsoft/TypeScript/issues/12473#issuecomment-263374060"&gt;pointed out&lt;/a&gt;, you can use existing code from the namespaced code and wrap it in the module file. You can also just export it as is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MyClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;my_namespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Since this setup is using ES modules, you can now use a bundler like webpack to prepare it for web consumption. In the following example, webpackwill be used with ts-loader. Notice ts-loader is configured to use the &lt;code&gt;tsconfig.module.json&lt;/code&gt; config file.&lt;/p&gt;

&lt;h4&gt;
  
  
  webpack.config.js
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/MyClass.m.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;tsx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
        &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
            &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ts-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
              &lt;span class="na"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tsconfig.module.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// Use the module project config!​&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bundle.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Finally, in your entry webpage, you can include a reference to the original namespace outFile and also the webpack bundle.&lt;/p&gt;

&lt;h4&gt;
  
  
  index.html
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;​
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/package.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- outFile output from tsc --&amp;gt;&lt;/span&gt;​
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/bundle.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- webpack bundle --&amp;gt;&lt;/span&gt;​
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once you’ve converted over all the namespaced source files to modules, you can remove the original namespace project and stop loading it.&lt;/p&gt;

&lt;p&gt;A more exhaustive example can be found in this repository: &lt;a href="https://github.com/bradymholt/ts-progressive-convert-namespace-modules"&gt;https://github.com/bradymholt/ts-progressive-convert-namespace-modules&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I found a use for a zero-width space</title>
      <dc:creator>Brady Holt</dc:creator>
      <pubDate>Fri, 30 Aug 2019 03:29:07 +0000</pubDate>
      <link>https://forem.com/bradymholt/i-found-a-use-for-a-zero-width-space-22jm</link>
      <guid>https://forem.com/bradymholt/i-found-a-use-for-a-zero-width-space-22jm</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ntsCXRnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/yzzqvktww2tffpca24th.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ntsCXRnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/yzzqvktww2tffpca24th.png" alt="Zero-width space"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You know about &lt;a href="https://en.wikipedia.org/wiki/Zero-width_space"&gt;zero-width spaces&lt;/a&gt; right?  It's an invisible character that seems not so useful at first glace.&lt;br&gt;
Most of my experience with them has been negative because they usually show up in a random file I'm parsing and it is the cause of a bizarre bug. Or, it's been the cause of a copy/paste search that yields no results even though there are matches sitting in front of my eyes.&lt;/p&gt;

&lt;p&gt;Well, despite my apathy for them, I actually found a good use for them.&lt;/p&gt;

&lt;p&gt;I've recently been building out &lt;a href="https://github.com/bradymholt/xertz"&gt;xertz&lt;/a&gt;, a static site generator written in TypeScript.  It's being used to build this here site.  Something that had been bugging me was the indentation of the rendered HTML.  I use Handlebars.js templates and include raw HTML which has been converted from Markdown.  My templates looks something like the following, where &lt;code&gt;{{ content_html }}&lt;/code&gt; is the raw HTML bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  {{&amp;gt; header }}
  {{&amp;gt; sidebar }}
  &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{{ content_html }}}
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  {{&amp;gt; footer }}
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The problem is, newlines in &lt;code&gt;{{ content_html }}&lt;/code&gt; do not get indented so the actual output looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;My Site&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;Sidebar here&lt;span class="nt"&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Lorem ipsum dolor sit amet, usu an justo deterruisset. Est ad discere nominati,
erroribus dissentias mei ne, appetere qualisque eloquentiam sea et.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"An image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"my-image.jpg"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;Lorem ipsum
dolor sit amet, usu an justo deterruisset. Est ad discere nominati&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;My footer&lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's a simple example.  In practice it's much worse.  Yes, this has no effect on the actual layout and rendering of the webpage&lt;br&gt;
but I'm a developer and care about the source and what it looks like.&lt;/p&gt;

&lt;p&gt;I tried to create a Handlebars helper called "indent" so I could indent each newline.  I called it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{{ indent content_html 2 }}}
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The helper is pretty simple.  It just replaces newlines (\n) with a newline followed by a number of spaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;intendation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That worked pretty well until my &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; code blocks started looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const my_var;
    const another_var;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The HTML looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;My Site&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;Sidebar here&lt;span class="nt"&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Below is a code block&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&amp;lt;pre&amp;gt;&lt;/span&gt;const my_var;
    const another_var;&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;Lorem ipsum dolor sit amet, usu an justo deterruisset. Est ad discere nominati,
    dissentias mei ne&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;My footer&lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Uh oh, I'm getting indentation in my code blocks.  Yes, since my code blocks are using &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; any spaces inside of them will be rendered as is. &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; means "preformatted" after all.&lt;/p&gt;

&lt;p&gt;So then I thought I needed &lt;em&gt;hint&lt;/em&gt; to signal to the &lt;strong&gt;indent&lt;/strong&gt; helper to skip indentation in these &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;Adding hints in the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tags seemed feasible because I use &lt;a href="https://prismjs.com/"&gt;Prism&lt;/a&gt; with &lt;a href="https://github.com/markedjs/marked"&gt;Marked&lt;/a&gt; to convert Markdown code blocks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```javascript
const my_var = "Hello";
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;into &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; blocks.  It's quite easy to modify the output of these tags because you provide a function that returns something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;pre class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;code class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Easy to modify, yes.  I thought, "can I add some character(s) to end of &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tags lines that my indent helper could skip?"  But since I'm using Regex to add the indentation in my &lt;strong&gt;indent&lt;/strong&gt; helper, I can only use a single character to be able to include a negated character (e.g. &lt;code&gt;[^!]&lt;/code&gt;) in my RegEx without having to do a negative look-behind (Javascript doesn't support these anyway).&lt;/p&gt;

&lt;p&gt;Ok, so, I just need Prism to add a single character that will not be visible to the end of lines that are inside of &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; blocks.  Then my &lt;strong&gt;indent&lt;/strong&gt; helper can ignore these.  How do I do this?&lt;/p&gt;

&lt;p&gt;Zero-width spaces, of course!&lt;/p&gt;

&lt;p&gt;Now, my code formatting function preceeds newlines in my code blocks with a zero-width space.  It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeWithNewlineHints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Prepend each newline with a zero-width space character so we can signal to any upstream formatting to leave the formatted code alone.&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u200b&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;pre class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;code class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;codeWithNewlineHints&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In my &lt;strong&gt;indent&lt;/strong&gt; helper, I simply ignore lines containing these characters preceding a newline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\u&lt;/span&gt;&lt;span class="sr"&gt;200b&lt;/span&gt;&lt;span class="se"&gt;]\n&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;intendation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See that RegEx there?  &lt;code&gt;/[^\u200b]\n/&lt;/code&gt; means only match newlines if they are &lt;em&gt;not&lt;/em&gt; preceded by a zero-width character (\u200b).  So, with this, indentation will only be added to lines not preceeded by these characters.&lt;/p&gt;

&lt;p&gt;I've gained a newfound respect zero-width spaces.&lt;/p&gt;

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