<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Node on Stuart Forrest</title><link>https://www.uglydirtylittlestrawberry.co.uk/categories/node/</link><description>Recent content in Node on Stuart Forrest</description><generator>Hugo -- gohugo.io</generator><language>en-uk</language><lastBuildDate>Tue, 07 Sep 2021 05:33:23 +0000</lastBuildDate><atom:link href="https://www.uglydirtylittlestrawberry.co.uk/categories/node/" rel="self" type="application/rss+xml"/><item><title>Hugo pretty URLs - migrating from Lambda@Edge to Cloudfront Functions</title><link>https://www.uglydirtylittlestrawberry.co.uk/posts/pretty-urls-cloudfront-functions/</link><pubDate>Tue, 07 Sep 2021 05:33:23 +0000</pubDate><guid>https://www.uglydirtylittlestrawberry.co.uk/posts/pretty-urls-cloudfront-functions/</guid><description>&lt;p>This post is intended to share the process of migrating URL prettifying functionality for a static site hosted in AWS to Cloudfront Functions from Lambda@Edge and deploying them with Serverless.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="#a-bit-of-background">Some background on my site&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-are-cloudfront-functions">What are Cloudfront Functions?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-is-the-serverless-framework">What is the Serverless Framework?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#migrating-pretty-urls-from-lambdaedge-to-cloudfront-functions">Migrating from Lambda@Edge to Cloudfront Functions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#summary">Summary&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="a-bit-of-background">A bit of background&lt;/h2>
&lt;p>This website is built using a static site generator called &lt;a href="https://gohugo.io/">Hugo&lt;/a>, deployed using the Serverless Framework and hosted in AWS using &lt;a href="https://aws.amazon.com/s3/">S3 for storage&lt;/a>, &lt;a href="https://aws.amazon.com/cloudfront/">Cloudfront as a CDN&lt;/a> and up until recently &lt;a href="https://aws.amazon.com/lambda/edge/">Lambda@Edge&lt;/a>. At build time all content is rendered into static HTML and CSS files, there is no client side magic that something like React provides. Hugo produces an &lt;code>index.html&lt;/code> for every post in it&amp;rsquo;s own folder, I was using Lambda@Edge to transform more friendly paths like &lt;code>/some-fancy-post-name&lt;/code> into &lt;code>/some-fancy-post-name/index.html&lt;/code> that S3 understands so that the content can actually be found. It is nothing fancy, just appending &lt;code>index.html&lt;/code> to any path that doesn&amp;rsquo;t have it.&lt;/p>
&lt;h2 id="what-are-cloudfront-functions">What are Cloudfront Functions?&lt;/h2>
&lt;p>&lt;a href="https://aws.amazon.com/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/">Cloudfront
Functions&lt;/a>,
whilst not a successor to Lambda@Edge, are an evolution of them. Whereas Lambda@Edge allows fully fledged lambda
functions to run within a regional edge cache (AWS region) closest to the request origin, Cloudfront Functions run right
at the edge where the content is ideally going to be served from. In terms of Cloudfront architecture you cannot get
closer to the user. There are some &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html">restrictions to Cloudfront
Functions&lt;/a>
you have to bear in mind like having a limited runtime, execution time, memory allowance, package size and that they are
javascript only with no network access.&lt;/p>
&lt;h2 id="what-is-the-serverless-framework">What is the Serverless Framework?&lt;/h2>
&lt;p>The &lt;a href="https://serverless.com/">Serverless Framework&lt;/a> is an infrastructure-as-code tool that simplifies the defining and deployment of mostly event driven serverless architectures. When used with AWS it is an abstraction over &lt;a href="https://aws.amazon.com/cloudformation/">Cloudformation&lt;/a> and does a lot of the heavy lifting to create and wire up resources like lambdas, queues, apis and events. As it compiles down to Cloudformation it can support other resources too but these are defined in &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html">regular Cloudformation syntax&lt;/a>.&lt;/p>
&lt;h2 id="migrating-pretty-urls-from-lambdaedge-to-cloudfront-functions">Migrating pretty URLs from Lambda@Edge to Cloudfront Functions&lt;/h2>
&lt;p>Migrating the Lambda@Edge that handled pretty URLs to a Cloudfront Function was pretty straight forward. The code itself went from this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">exports&lt;/span>.&lt;span style="color:#a6e22e">handler&lt;/span> &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#a6e22e">event&lt;/span>, &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>&lt;span style="color:#a6e22e">_&lt;/span>, &lt;span style="color:#a6e22e">callback&lt;/span>) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">event&lt;/span>.&lt;span style="color:#a6e22e">Records&lt;/span>[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#a6e22e">cf&lt;/span>.&lt;span style="color:#a6e22e">request&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> RegExp(&lt;span style="color:#e6db74">&amp;#34;/$&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">match&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span>.&lt;span style="color:#a6e22e">exec&lt;/span>(&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">match&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">`&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">index.html`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">callback&lt;/span>(&lt;span style="color:#66d9ef">null&lt;/span>, &lt;span style="color:#a6e22e">request&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>to this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">function&lt;/span> &lt;span style="color:#a6e22e">handler&lt;/span>(&lt;span style="color:#a6e22e">event&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">event&lt;/span>.&lt;span style="color:#a6e22e">request&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> RegExp(&lt;span style="color:#e6db74">&amp;#34;/$&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">match&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span>.&lt;span style="color:#a6e22e">exec&lt;/span>(&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">match&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">`&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">index.html`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>As you can see very little changed, sourcing the value for &lt;code>request&lt;/code> was the only functional change to the code required. Some other changes were required because of the restricted runtime for Cloudfront Functions:&lt;/p>
&lt;ul>
&lt;li>No exports allowed!&lt;/li>
&lt;li>No &lt;code>let&lt;/code> or &lt;code>const&lt;/code>, not used good old fashioned hoistable &lt;code>var&lt;/code> in a while&lt;/li>
&lt;/ul>
&lt;p>The &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html">feature availability&lt;/a> in the Cloudfront Function runtime is a real jumble of pre-ES5 plus a bit of ES6, ES7 and beyond. And no exports! I was keen to unit test these functions and the lack of exports presented a challenge, I will write up how I went about that in a future post.&lt;/p>
&lt;p>Cloudfront Functions code is also required to be inline in the CloudFormation template. This isn&amp;rsquo;t great from a testability and IDE syntax linting/hinting/etc points of view. Also, only one function per trigger is allowed which makes reusability of the code a challenge. For example with my development version of the site I expected to be able to use two Functions in a pipeline on the &lt;code>viewer-request&lt;/code> trigger, one for HTTP Basic Auth and one for pretty URLs, then only pretty URLs in production. Alas, the code had to be duplicated, and this limitation already exists with Lambda@Edge.&lt;/p>
&lt;p>For the inline code problem, I was fortunate enough to be using a Typescript file to define my Serverless config instead of &lt;code>yml&lt;/code> so I just read an external file containing the full code for my Cloudfront Function:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">fs&lt;/span>.&lt;span style="color:#a6e22e">readFileSync&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;./lambdas/cfFunctions/prettyUrls.js&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;utf8&amp;#34;&lt;/span>)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This worked like a charm but is there an alternative? Yes, use CDK, which handles reading the code in from a file for you (&lt;a href="https://docs.aws.amazon.com/cdk/api/latest/typescript/api/aws-cloudfront/functioncode.html#aws_cloudfront_FunctionCode_fromFile">docs&lt;/a>).&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>Migrating from Lambda@Edge to Cloudfront Functions was an opportunity to try something new from AWS and push some compute as close to the end user as possible. It went really well and the bulk of the time I spent doing it was spent figuring out unit testing (I wrote about that &lt;a href="https://www.uglydirtylittlestrawberry.co.uk/posts/unit-testing-cloudfront-functions/">here&lt;/a>) and a clean deployment strategy as opposed to writing the code and just getting it deployed.&lt;/p>
&lt;p>For simple uses that manipulate the request/response objects without the need for external input then Cloudfront Functions are a better alternative to Lambda@Edge, otherwise Lambda@Edge isn&amp;rsquo;t going anywhere. For those simple use cases migrating between the two is pretty straight forward though deployment and a robust development lifecycle for Cloudfront Functions can have a little bit more complexity.&lt;/p>
&lt;h3 id="get-in-contact">Get in contact&lt;/h3>
&lt;p>If you have comments, questions or better ways to do anything that I have discussed in this post then please get in contact via &lt;a href="https://linkedin.com/in/stuart-f-41a43b180">LinkedIn&lt;/a> or &lt;a href="mailto:stuart@uglydirtylittlestrawberry.co.uk">email&lt;/a>.&lt;/p></description></item><item><title>Using path aliases in VSCode in Javascript or Typescript projects with Jest and Webpack</title><link>https://www.uglydirtylittlestrawberry.co.uk/posts/javascript-typescript-path-aliases/</link><pubDate>Fri, 28 May 2021 11:33:23 +0000</pubDate><guid>https://www.uglydirtylittlestrawberry.co.uk/posts/javascript-typescript-path-aliases/</guid><description>&lt;p>This post might be helpful if you are looking to use path aliases within a javascript or typescript project.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="#what-are-path-aliases">What are path aliases?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#why-might-you-want-to-use-them">Why use path aliases?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#typescript-and-ide-our-source-of-truth">Configuring Typescript and your IDE&lt;/a>&lt;/li>
&lt;li>&lt;a href="#jest">Configuring Jest&lt;/a>&lt;/li>
&lt;li>&lt;a href="#webpack">Configuring Webpack&lt;/a>&lt;/li>
&lt;li>&lt;a href="#other-options">Any other options?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#demo-repository">Demo repository&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="what-are-path-aliases">What are path aliases?&lt;/h2>
&lt;p>Path aliases are the mapping of some kind of shorthand identifier to an import path (usually) within your project.&lt;/p>
&lt;p>A relative import path like &lt;code>../../data/types&lt;/code> could use an alias like &lt;code>@shared/data/types&lt;/code>.&lt;/p>
&lt;p>In effect, whenever your tooling encounters the shorthand reference it will know where to look to actually import the desired module.&lt;/p>
&lt;h2 id="why-might-you-want-to-use-them">Why might you want to use them?&lt;/h2>
&lt;h3 id="improved-readability">Improved readability&lt;/h3>
&lt;p>There is a cognitive load when importing relative modules in your code using explicitly relative paths. It will usually look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">maxCacheAgeInMS&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;../../../config&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">CompanySummary&lt;/span>, &lt;span style="color:#a6e22e">DynamoDBQueryRecord&lt;/span>, &lt;span style="color:#a6e22e">Product&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;../../data/types&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> &lt;span style="color:#a6e22e">chunkArray&lt;/span> &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;../../utils/chunk&amp;#34;&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Whilst there is nothing wrong with this why not look at the alternative. What if, instead of a relative path to our &lt;code>shared&lt;/code> folder being used every time we want to import an module from there we could use a fixed shorthand? Then it could look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">maxCacheAgeInMS&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;@config&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">CompanySummary&lt;/span>, &lt;span style="color:#a6e22e">DynamoDBQueryRecord&lt;/span>, &lt;span style="color:#a6e22e">Product&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;@shared/data/types&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> &lt;span style="color:#a6e22e">chunkArray&lt;/span> &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;@shared/utils/chunk&amp;#34;&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>In this example &lt;code>@shared&lt;/code> is a path alias that has been setup to map to &lt;code>[projectRoot]/src/shared&lt;/code> meaning that wherever we use it within our project our toolchain of choice will be able to find and import the same module.&lt;/p>
&lt;p>I prefer the readability of this setup and find the cognitive load of reading it much smaller.&lt;/p>
&lt;h3 id="encapsulation">Encapsulation&lt;/h3>
&lt;p>When we use path aliases it can make it easier for us to refactor or change our project structure, if we wanted to move the location of our &lt;code>shared&lt;/code> modules we can update the alias mapping in one place.&lt;/p>
&lt;p>Whilst most modern text editors-cum-IDEs (VSCode included) can handle this for us by monitoring our project structures and updating imports for us when it changes it doesn’t always work as expected (especially in large projects/codebases) and often has some developer overhead of checking and confirming each change is valid.&lt;/p>
&lt;p>Using path aliases within a project can help encapsulate this location logic within the codebase itself. On it’s own this is not the strongest argument for path aliases but builds on the benefits of increased readability.&lt;/p>
&lt;h2 id="what-is-the-catch">What is the catch?&lt;/h2>
&lt;p>There is no such thing as a free lunch and whilst using path aliases comes with some benefits there is some setup and configuration required. There are some potential additional headaches in store for the tooling that is used around your project like:&lt;/p>
&lt;ul>
&lt;li>Building/bundling, eg. babel, esbuild, tsc, webpack etc.&lt;/li>
&lt;li>Testing, eg. jest etc.&lt;/li>
&lt;li>IDE support, eg. VSCode, Webstorm etc.&lt;/li>
&lt;/ul>
&lt;p>This post focuses on the use of path aliases with:&lt;/p>
&lt;ul>
&lt;li>VSCode&lt;/li>
&lt;li>Jest&lt;/li>
&lt;li>Webpack&lt;/li>
&lt;li>Typescript&lt;/li>
&lt;/ul>
&lt;p>For the most part the configurations can be defined once and shared between these tools, this is great and reduces the potential headaches (and complexity) or setting up and maintaining path aliases in multiple places.&lt;/p>
&lt;h2 id="configuring-path-aliases">Configuring path aliases&lt;/h2>
&lt;p>Remember that path aliases are essentially a mapping between a shorthand reference and a qualified path. When configuring path aliases this is usually represented as a &lt;code>key -&amp;gt; value&lt;/code> mapping that is observed by your tooling. The remaining questions are where does it go and what does it look like? We will walk through the answers to those now.&lt;/p>
&lt;h3 id="typescript-and-ide-our-source-of-truth">Typescript and IDE (our source of truth)&lt;/h3>
&lt;p>Typescript has the concept of projects and all your code must belong to a project, ordinarily this root of your repository represents the root of your project but not always. The Typescript compiler, &lt;code>tsc&lt;/code>, looks for &lt;a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html">tsconfig.json&lt;/a> files below the directory from which it has been run in or pointed at. Every directory in which it finds a tsconfig.json file is treated as the root of a separate Typescript project. For the purposes of this post we will assume you are working with a single Typescript project. Note that if you have multiple projects your aliases defined in a tsconfig.json are only valid within the scope of that project.&lt;/p>
&lt;p>Whereas Typescript expects a &lt;code>tsconfig.json&lt;/code> file to be present, pure Javascript projects don’t require an analogous &lt;code>jsconfig.json&lt;/code> file. The concept of a &lt;code>jsconfig.json&lt;/code> file was &lt;a href="https://code.visualstudio.com/docs/languages/jsconfig">introduced by VSCode&lt;/a> and modelled on the &lt;a href="https://www.typescriptlang.org/tsconfig">tsconfig.json spec&lt;/a> used by Typescript. VSCode uses this file to, amongst other things, help Intellisense make sense of path aliases. Whilst it was introduced by VSCode it has been adopted or supported by other IDEs like Webstorm (&lt;a href="https://youtrack.jetbrains.com/issue/WEB-36390">since 2019.2&lt;/a>).&lt;/p>
&lt;h4 id="the-config">The config&lt;/h4>
&lt;p>There is a &lt;code>paths&lt;/code> property under &lt;code>compilerOptions&lt;/code> in the &lt;a href="https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping">tsconfig.json spec&lt;/a> as you can see below. This is where our alias mappings should go:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;compilerOptions&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;baseUrl&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;./&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;paths&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;@shared/*&amp;#34;&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;src/shared/*&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;@config&amp;#34;&lt;/span>: [&lt;span style="color:#e6db74">&amp;#34;config.ts&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>In this example you can see that you can point an alias to a specific file if you want to though usually a wildcard (&lt;code>*&lt;/code>) is included that will be expanded to the remainder of the import path after the alias value. For example &lt;code>@shared/data/types&lt;/code> would be expanded to &lt;code>src/shared/data/types&lt;/code> at module resolution time.&lt;/p>
&lt;p>There is also a &lt;a href="https://www.typescriptlang.org/tsconfig#baseUrl">&lt;code>baseUrl&lt;/code>&lt;/a> property under &lt;code>compilerOptions&lt;/code>, if set the path aliases values are relative to this.&lt;/p>
&lt;p>At this point the Typescript compiler and VSCode should quite happily be able to consume our path aliases within our project. VSCode may need a few minutes in a particularly big project, alternatively you can reload the project from the Command Palette.&lt;/p>
&lt;h3 id="single-source-of-truth">Single source of truth&lt;/h3>
&lt;p>Our &lt;code>tsconfig.json&lt;/code> or &lt;code>jsconfig.json&lt;/code> will be used as the single source of truth for our path aliases within our project. How this is achieved for the rest of out toolchain varies from tool to tool but there are some handy helper modules we can use to make it relatively straight forward.&lt;/p>
&lt;h3 id="jest">Jest&lt;/h3>
&lt;p>Unless otherwise specified Jest will assume your configuration is within your &lt;code>package.json&lt;/code> file or a standalone &lt;code>jest.config.js&lt;/code> file.&lt;/p>
&lt;p>The Jest configuration property, &lt;code>moduleNameMapper&lt;/code>, can be set and will inform Jest how to manipulate imports as required. This includes handling path aliases like those set up in the previous step. This property can look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">moduleNameMapper&lt;/span>&lt;span style="color:#f92672">:&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;^@shared/(.*)&amp;#34;&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;lt;rootDir&amp;gt;/src/shared/$1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;^@config$&amp;#34;&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;lt;rootDir&amp;gt;/config.ts&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>A glob/regex is used to match against import paths and mapped to one or more possible locations where these modules may be imported from.&lt;/p>
&lt;p>I should note here that the &lt;code>moduleNameMapper&lt;/code> configuration is not specifically for handling path aliases, it is intended for any use case where modifying imports in the test environment is desirable. For more information see &lt;a href="https://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring">the documentation&lt;/a>.&lt;/p>
&lt;h4 id="using-our-single-source-of-truth">Using our single source of truth&lt;/h4>
&lt;p>We are using our &lt;code>tsconfig.json&lt;/code> or &lt;code>jsconfig.json&lt;/code> files as a source of truth for our path aliases. With jest that are a couple of ways to do this.&lt;/p>
&lt;p>If you are using &lt;code>ts-jest&lt;/code> it comes bundled with a handy utility function that can be used to transform the alias config from your &lt;code>tsconfig.json&lt;/code> into what Jest requires. Usage is as follows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#a6e22e">pathsToModuleNameMapper&lt;/span> } &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">require&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;ts-jest/utils&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#a6e22e">compilerOptions&lt;/span> } &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">require&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;./tsconfig&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">moduleNameMapper&lt;/span>: &lt;span style="color:#66d9ef">pathsToModuleNameMapper&lt;/span>(&lt;span style="color:#a6e22e">compilerOptions&lt;/span>.&lt;span style="color:#a6e22e">paths&lt;/span>, {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">prefix&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;lt;rootDir&amp;gt;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>As you can see passing a prefix of “&amp;lt;rootDir&amp;gt;” is often required as that is Jest’s representation of the root of your project and the mapper utility makes no assumptions about prefixes for your paths.&lt;/p>
&lt;p>If you are not using &lt;code>ts-jest&lt;/code> or do not want to couple yourself to &lt;code>ts-jest&lt;/code> in this manner there is a standalone npm module &lt;a href="https://github.com/sebastianmusial/jest-module-name-mapper">&lt;code>jest-module-name-mapper&lt;/code>&lt;/a> that does the job as well. Usage is as follows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#66d9ef">default&lt;/span>&lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#a6e22e">tsconfigTransformer&lt;/span> } &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">require&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;jest-module-name-mapper&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">moduleNameMapper&lt;/span>: &lt;span style="color:#66d9ef">tsconfigTransformer&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;./tsconfig.json&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// OR (as per the readme)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">moduleNameMapper&lt;/span>: &lt;span style="color:#66d9ef">require&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;jest-module-name-mapper&amp;#34;&lt;/span>)(&lt;span style="color:#e6db74">&amp;#34;./tsconfig.json&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>As you can see the usage is pretty much similar. The main difference is that &lt;code>jest-module-name-mapper&lt;/code> does make some sensible assumptions about the prefix jest requires for your paths and also about the location of your &lt;code>tsconfig.json&lt;/code> file. I’ve passed in the location explicitly for clarity but this would have been the assumed location anyway.&lt;/p>
&lt;h3 id="webpack">Webpack&lt;/h3>
&lt;p>If you are using Webpack then it too will need to know how to handle your path aliases. There is a webpack configuration property &lt;code>resolve&lt;/code> which has a child property &lt;code>alias&lt;/code> (as seen below, &lt;a href="https://webpack.js.org/configuration/resolve/#resolvealias">docs&lt;/a>), this is where we can let Webpack know how to handle our aliases. As with jest there is a handy npm module (or two) that can use your &lt;code>tsconfig.json&lt;/code> as the source of truth for your aliases. These are:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/dividab/tsconfig-paths-webpack-plugin">&lt;code>tsconfig-paths-webpack-plugin&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/arzyu/resolve-ts-aliases">&lt;code>resolve-ts-aliases&lt;/code>&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>I prefer the latter as the former replaces the context of &lt;code>resolve.aliases&lt;/code> making it difficult to add aliases in addition to those within your &lt;code>tsconfig.json&lt;/code>. Ultimately choosing which to use should be a decision based on the needs and context of your use case. Below is the usage of &lt;code>resolve-ts-aliases&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ts" data-lang="ts">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">import&lt;/span> { &lt;span style="color:#a6e22e">resolveTsAliases&lt;/span> } &lt;span style="color:#66d9ef">from&lt;/span> &lt;span style="color:#e6db74">&amp;#34;resolve-ts-aliases&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">config&lt;/span>: &lt;span style="color:#66d9ef">webpack.Configuration&lt;/span> &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">resolve&lt;/span>&lt;span style="color:#f92672">:&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">alias&lt;/span>: &lt;span style="color:#66d9ef">resolveTsAliases&lt;/span>(&lt;span style="color:#a6e22e">path&lt;/span>.&lt;span style="color:#a6e22e">resolve&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;tsconfig.json&amp;#34;&lt;/span>)),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>How you are using Webpack in your project will determine whether this alias mapping will be required, but if you find that Webpack cannot resolve your aliases the configuration above should hopefully do the trick.&lt;/p>
&lt;h2 id="other-options">Other options?&lt;/h2>
&lt;p>Most Javascript projects of a certain size, especially anything being shipped out to the world, will have some build tooling. The common build tools have been covered here but if you have a project with no tooling, for example you intend to run your script with &lt;code>node index.js&lt;/code> then the npm module &lt;a href="https://github.com/ilearnio/module-alias">&lt;code>module-alias&lt;/code>&lt;/a> can be used. It intercepts node’s module resolution process to resolve any known aliases. Unfortunately, whilst it can play nice with webpack it struggles to do so with Jest (because Jest bypasses node&amp;rsquo;s module resolution) and may present challenges with other toolchains. How to use path aliases in this way is out of scope of this post but check it out if this sounds like it might be useful.&lt;/p>
&lt;h2 id="demo-repository">Demo repository&lt;/h2>
&lt;p>There is a repository with a tiny demo project with path aliases configured and working with Typescript, Jest and Webpack available &lt;a href="https://github.com/slcp/typescript-path-aliases">here&lt;/a>.&lt;/p>
&lt;h3 id="get-in-contact">Get in contact&lt;/h3>
&lt;p>If you have comments, questions or better ways to do anything that I have discussed in this post then please get in contact via &lt;a href="https://linkedin.com/in/stuart-f-41a43b180">LinkedIn&lt;/a> or &lt;a href="mailto:stuart@uglydirtylittlestrawberry.co.uk">email&lt;/a>.&lt;/p></description></item></channel></rss>