<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zack DeRose</title>
    <description>The latest articles on DEV Community by Zack DeRose (@zackderose).</description>
    <link>https://dev.to/zackderose</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%2F126200%2Fc43736b5-6ece-46c5-a3bd-4fb1a5e7f845.jpg</url>
      <title>DEV Community: Zack DeRose</title>
      <link>https://dev.to/zackderose</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zackderose"/>
    <language>en</language>
    <item>
      <title>Launch Nx Week Recap!!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Thu, 15 Feb 2024 18:09:12 +0000</pubDate>
      <link>https://dev.to/nx/launch-nx-week-recap-51og</link>
      <guid>https://dev.to/nx/launch-nx-week-recap-51og</guid>
      <description>&lt;p&gt;We just finished wrapping up &lt;a href="https://nx.dev/launch-nx"&gt;Launch Nx Week&lt;/a&gt;, which ran from February 5-9, including a full conference on Thursday!&lt;/p&gt;

&lt;p&gt;In this article, we're going to recap all the things launched during Launch Nx Conf, as well as all the talks given during the conference! Here's a set of links so you can fast-forward to the stuff you're most interested in if you want!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Nx 18.0 &amp;amp;&amp;amp; Project Crystal

&lt;ul&gt;
&lt;li&gt;Project Crystal - By Juri Strumpflohner&lt;/li&gt;
&lt;li&gt;Project Crystal + .NET in Action - By Craigory Coppola&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;New Plugin: @nx/nuxt&lt;/li&gt;
&lt;li&gt;
Nx Agents

&lt;ul&gt;
&lt;li&gt;Nx Agents Walkthrough: Effortlessly Fast CI Built for Monorepos - By Rares Matei&lt;/li&gt;
&lt;li&gt;Solving E2E Tests - By Altan Stalker&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Tusky&lt;/li&gt;
&lt;li&gt;
Nx Release

&lt;ul&gt;
&lt;li&gt;Releasing Nx Release - By James Henry&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nx 18.0 &amp;amp;&amp;amp; Project Crystal&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Exciting news!! We've broken our standard release cadance of publishing a new major version every 6 months to release Nx 18, just 3 months after releasing Nx 17 in December 2024.&lt;/p&gt;

&lt;p&gt;Juri announced launched Project Crystal with this video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wADNsVItnsM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The short version is: Nx project crystal means Nx plugins make your codebases work more seemlessly with less config.&lt;/p&gt;

&lt;p&gt;For the long version, checkout this &lt;a href="https://blog.nrwl.io/what-if-nx-plugins-were-more-like-vscode-extensions-dcdad140ae09"&gt;blog post&lt;/a&gt; by Juri where he describes how Nx plugins are now more like VsCode Extentions!&lt;/p&gt;

&lt;p&gt;And don't miss our two conference talks on this subject:&lt;/p&gt;

&lt;h3&gt;
  
  
  Conference Talk: Project Crystal&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speaker:&lt;/strong&gt; &lt;a href="https://twitter.com/juristr"&gt;Juri Strumpflohner&lt;/a&gt; | &lt;a href="https://drive.google.com/file/d/1k-cGCJUMP4axcCWoeih8n3dvo1oO_i_X/view?usp=sharing"&gt;slides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/PzCgpM7qtTU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Conference Talk: Project Crystal + .NET in Action&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speaker:&lt;/strong&gt; &lt;a href="https://twitter.com/enderagent"&gt;Craigory Coppola&lt;/a&gt; | &lt;a href="https://docs.google.com/presentation/d/1uveIe6HB7xwSkh7FBGZfF8Unh5YZzm5X/edit?usp=sharing&amp;amp;ouid=109667724870581513512&amp;amp;rtpof=true&amp;amp;sd=true"&gt;slides&lt;/a&gt; | &lt;a href="https://github.com/AgentEnder/nx-launch-conf-demos"&gt;example repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/fh-yzOuQGE8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  New Plugin: @nx/nuxt&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;On Tuesday we launched our newest plugin, and our first plugin to be 💎crystalized💎 from it's very beginning: @nx/nuxt!&lt;/p&gt;

&lt;p&gt;Zack explains Nuxt and how to use the new plugin in this video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/1L-bDvEemoc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;To add @nx/nuxt to your codebase, use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nx add @nx/nuxt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Huge thanks to &lt;a href="https://twitter.com/psybercity"&gt;Katerina&lt;/a&gt; for her work on the plugin, and Nuxt maintainer, &lt;a href="https://twitter.com/danielcroe"&gt;Daniel Roe&lt;/a&gt;, for helping to guide the project!&lt;/p&gt;

&lt;p&gt;Zack, Katerina, and Daniel got together to live code a working tic-tac-toe app using the new Nuxt plugin and &lt;a href="https://www.partykit.io/"&gt;partykit&lt;/a&gt; in this livestream:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/uHwUxFYX2DY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Agents Launched&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx Agents are here!! We launched Nx Agents on Wednesday, and it climbed into the top 20 on &lt;a href="https://www.producthunt.com/posts/nx-agents"&gt;Product Hunt&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;We're very excited about Nx Agents because we think that in its current state, Continuous Integration/Deployment is broken, and we think that Nx paired with Nx Agents fixes Continuous Integration. Zack explains more in this video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/_FSHQIwITic"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Be sure to also checkout the &lt;a href="https://blog.nrwl.io/fast-effortless-ci-67812514ffb4"&gt;blog post&lt;/a&gt; from Isaac on the topic, including explanations of the Nx Agents exclusive features, like auto-detection and retrying for flaky tasks, and automatically splitting lengthy end-to-end tests!&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://nx.app/products/agents#content"&gt;signup for Nx Agents NOW&lt;/a&gt;, and &lt;a href="https://nx.dev/ci/features/distribute-task-execution"&gt;find out more of the details in our docs&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Rares and Altan are on the team building Nx Cloud, and during the conference, they dove deeper into some of these topics:&lt;/p&gt;

&lt;h3&gt;
  
  
  Conference Talk: Nx Agents Walkthrough: Effortlessly Fast CI Built for Monorepos&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speaker:&lt;/strong&gt; &lt;a href="https://twitter.com/__rares"&gt;Rares Matei&lt;/a&gt; | &lt;a href="https://drive.google.com/file/d/1k-cGCJUMP4axcCWoeih8n3dvo1oO_i_X/view?usp=drive_link"&gt;slides&lt;/a&gt; | &lt;a href="https://github.com/rarmatei/shops-workflows/pulls"&gt;example repo&lt;/a&gt; | &lt;a href="https://cloud.nx.app/cipes/65b27cf6d3ef5934decad746?utm_source=pull-request&amp;amp;utm_medium=comment"&gt;failed runs example&lt;/a&gt; | &lt;a href="https://cloud.nx.app/cipes/65b38179d3ef5934dede74c2?utm_source=pull-request&amp;amp;utm_medium=comment"&gt;nx agents full run&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/XS-exYYP_Gg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Conference Talk: Solving E2E Tests&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speaker:&lt;/strong&gt; &lt;a href="https://twitter.com/StalkAltan"&gt;Altan Stalker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EO_tGa0Nx1s"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Tusky&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Thursday, we had a surprise announcement for our newest product on Nx Cloud: Tusky.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Xfvv09wSoM8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Tusky is an Artificial Intelligence for your codebase, and it will be available on Nx Cloud soon - starting in Spring of 2024.&lt;/p&gt;

&lt;p&gt;In the teaser video, you can see some ways Tusky will be integrated into Nx Cloud to provide explainations on failed tasks, or cache misses.&lt;/p&gt;

&lt;p&gt;The more exciting features of Tusky though includes the ability to perfectly optimize the number of agents used per PR - even spinning up and tearing down agents mid-pipeline! This is a huge optimization for task distribution, and will help optimize both walltime of your CI, as well as compute costs!&lt;/p&gt;

&lt;p&gt;Tusky will also be able to provide organizational-level insights to your codebase, including automatically detecting development groups (like lines of business) inside your codebase based on commit history, and providing statistical insights on their commit patterns. We're also excited about Tusky being able to make suggestions on things you can do to further optimize your codebase - like generating a PR to introduce &lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"&gt;Codeowners&lt;/a&gt; to the repo!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Release Is Stable&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Versioning and publishing packages is always a bit tricky. Mix in the added complexity of having multiple packages - sometimes with different versioning or publishing strategies - inside the same codebase, and things can get weird quick!&lt;/p&gt;

&lt;p&gt;For a long time, Nx has been purposefully versioning and publishing agnostic, but given our time spent as &lt;a href="https://blog.nrwl.io/lerna-is-dead-long-live-lerna-61259f97dbd9"&gt;stewards of Lerna&lt;/a&gt; (the OG Javascript monorepo tool), we've been able to take alot of that experience and finally feel confident creating our own versioning and publishing implementation.&lt;/p&gt;

&lt;p&gt;Therefore, we've been working on a new command to the Nx CLI: &lt;a href="https://nx.dev/recipes/nx-release/get-started-with-nx-release#get-started-with-nx-release"&gt;nx release&lt;/a&gt;. We launched this on Friday of our Launch Nx week!&lt;/p&gt;

&lt;p&gt;Juri goes into full details in this &lt;a href="https://blog.nrwl.io/versioning-and-releasing-packages-in-a-monorepo-45ee194378d1"&gt;blog post&lt;/a&gt;, and James Henry - our Director of Engineering and the primary engineer responsible for both maintaining Lerna and creating Nx Release - expands further in his conference talk:&lt;/p&gt;

&lt;h3&gt;
  
  
  Conference Talk: Releasing Nx Release&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Speaker:&lt;/strong&gt; &lt;a href="https://twitter.com/MrJamesHenry"&gt;James Henry&lt;/a&gt; | &lt;a href="https://github.com/JamesHenry/nx-release-cmd"&gt;example repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KjZKFGu3_9I"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Juri, Zack, and Victor wrapped up the week with a livestream recapping the launches from the week - including answering your questions live:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xjLrFvEcxZw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That’s all for now folks! We’re just starting up a new iteration of development on Nx, so be sure to subscribe to our &lt;a href="https://www.youtube.com/@nxdevtools"&gt;YouTube channel&lt;/a&gt; to get updates when new features land! Until next time, KEEP WORKING HARD!&lt;/p&gt;

&lt;h3&gt;
  
  
  Learn more
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;a href="https://nx.dev"&gt;Nx Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👩‍💻 &lt;a href="https://github.com/nrwl/nx"&gt;Nx GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://go.nx.dev/community"&gt;Nx Official Discord Server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📹 &lt;a href="https://www.youtube.com/@nxdevtools"&gt;Nx Youtube Channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://nx.app"&gt;Speed up your CI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nx</category>
      <category>monorepos</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Nx 17.2 Update!!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Wed, 20 Dec 2023 17:02:39 +0000</pubDate>
      <link>https://dev.to/nx/nx-172-update-5ag8</link>
      <guid>https://dev.to/nx/nx-172-update-5ag8</guid>
      <description>&lt;p&gt;It's been a bit since we launched &lt;a href="https://blog.nrwl.io/nx-17-0-has-landed-1dad19176ff3"&gt;Nx 17&lt;/a&gt;! In this article, we'll go over some of the new developments and improvements that have landed in Nx 17.2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nx Closes In On 4 Million Weekly NPM Downloads!!&lt;/li&gt;
&lt;li&gt;New Simplified Project Configuration On the Way&lt;/li&gt;
&lt;li&gt;Rust for Speed, Typescript for extensibility&lt;/li&gt;
&lt;li&gt;Module Federation Updates&lt;/li&gt;
&lt;li&gt;Nx Release Updates&lt;/li&gt;
&lt;li&gt;Angular 17 (AND NgRx 17) Support&lt;/li&gt;
&lt;li&gt;Smart Monorepos - Fast CI&lt;/li&gt;
&lt;li&gt;New Canary Releases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nx Closes In On 4 Million Weekly NPM Downloads!!&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;2023 has been a great year for Nx! We worked with a lot of the teams out there building fantastic open source tools. And you can see the results: we made &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; a first class citizen in many of our Nx plugins, we added support for &lt;a href="https://youtu.be/jGTE7xAcg24?si=gyFyW_uYjMq_5ELF"&gt;Rspack&lt;/a&gt;, streamlined our Node experience by adding an Nx team maintained &lt;a href="https://youtu.be/LHLW0b4fr2w?si=P5MPIiD_mxTpQStY"&gt;Fastify plugin&lt;/a&gt;, support for Storybook interaction testing, welcomed Playwright to the family and much more, continuing our missing to push developer productivity to the limits!&lt;/p&gt;

&lt;p&gt;And our downloads on NPM keep confirming this. We are about to cross 4 million downloads per week.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrlp1bucdu4qsjdwcipj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrlp1bucdu4qsjdwcipj.png" alt="Almost 4 million weekly downloads!" width="470" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this made you curious, keep an eye on &lt;a href="https://nx.dev/blog"&gt;our blog&lt;/a&gt; or &lt;a href="https://twitter.com/nxdevtools"&gt;X/Twitter&lt;/a&gt; as we're going to release a 2023 year recap blog post next week.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Simplified Project Configuration On the Way&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Adoption is crucial, and simplicity is the driver for adoption. Last year we heavily optimized how you can use Nx in an existing project. Just drop the &lt;code&gt;nx&lt;/code&gt; package (or run &lt;code&gt;nx init&lt;/code&gt;) and that's it. Nx understands your workspace and efficiently runs your &lt;code&gt;package.json&lt;/code&gt; scripts.&lt;/p&gt;

&lt;p&gt;Using Nx at that level is definitely useful as you get intelligent parallelization, task pipelines and caching. But it is just the tip of the iceberg of what Nx is actually capable of. Nx plugins provide much more, especially in terms of DX and developer productivity by taking away some of the burden of configuring your monorepo tooling. But many devs new to Nx found it harder to get started with them initially and incrementally migrating to Nx plugins wasn't as straightforward as we'd wanted it to be.&lt;/p&gt;

&lt;p&gt;This is something that's gonna change drastically in 2024. And we've layed the first cornerstone for that. Nx 17.2 includes what we codename "Nx project config v3". But it is behind a feature flag still as we're streamlining the last bits. The goal?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;going almost configuration-less (good defaults, you customize when you need to)&lt;/li&gt;
&lt;li&gt;allowing easy drop-in of Nx plugins into existing workspaces (provides immediate productivity gains, but stays out of your way)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This opens up a series of possibilities which we're already super excited about. You'll hear more about this in the new year ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust for Speed, Typescript for extensibility&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;At Nx, we've heavily embraced Typescript from the beginning, and we've been very happy with that decision.&lt;/p&gt;

&lt;p&gt;If you've been paying attention to previous release announcements though, you've probably noticed that we've been moving more and more of the computationally intensive and performance critical pieces of the core of Nx from Typescript to Rust.&lt;/p&gt;

&lt;p&gt;That trend continues in Nx 17.2 with Nx &lt;a href="https://github.com/nrwl/nx/pull/19617"&gt;using Rust for its task hashing by default&lt;/a&gt;. There's no adjustments needed for this change, and Nx will continue to behave the same way, just faster!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1724464283227234420-322" src="https://platform.twitter.com/embed/Tweet.html?id=1724464283227234420"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1724464283227234420-322');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1724464283227234420&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Module Federation Updates&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Module Federation has been a particularly hot topic lately, and 17.2 is coming in with some great enhancements to Nx's already best-in-class support for Module Federation!&lt;/p&gt;

&lt;p&gt;To start, we've greatly reduced the CPU and memory used for standing up your Module Federation "web" locally. These enhancements should be great news to larger workspaces using a Module Federation approach, where there were heavy CPU and memory costs to serving your entire federation locally.&lt;/p&gt;

&lt;p&gt;We accomplished these improvements by batching any applications that are not being watched for changes (listed with the &lt;code&gt;--devRemotes&lt;/code&gt; option) to a single server, rather than a unique server for each application. We also parallelized the builds of these static applications when you start up your serve! You can now use the &lt;code&gt;--parallel={number}&lt;/code&gt; option to specify how many builds you want going at any given time.&lt;/p&gt;

&lt;p&gt;In addition to performance improvements, we've brought the concept of dynamic federation to our React module federation support. Dynamic federation allows a host application to dynamically load remotes via the manifest file.&lt;/p&gt;

&lt;p&gt;You can generate your react module federation workspace now to use dyanmic federation via the &lt;code&gt;--dynamic&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nx generate @nx/react:host acme --remotes=nx --dynamic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can use the utility itself by importing from &lt;code&gt;@nx/react/mf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadRemoteModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nx/react/mf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, we have an &lt;a href="https://github.com/jaysoo/nx-react-vite-module-federation"&gt;example repo&lt;/a&gt; available now to illustrate how to create a plugin for Module Federation using Nx with Vite! This is something that we are monitoring and may provide an out-of-the-box solution to this in a future release!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Release Updates&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx 17 launched with the new &lt;code&gt;nx release&lt;/code&gt; capability in the Nx CLI. Since then we've been streamlining the experience, accounting for various edge cases and release scenarios. (extensive docs are being worked on rn ;)&lt;/p&gt;

&lt;p&gt;To give you full flexibility, in 17.2, we've added a programmatic API, which will allow you to easily write custom release scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;releaseChangelog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;releasePublish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;releaseVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nx/release&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;workspaceVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;projectsVersionData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;releaseVersion&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;specifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;releaseChangelog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;versionData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;projectsVersionData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;workspaceVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;releasePublish&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script above demonstrates how you can use this API to create your own script for updating your workspace's version, then creating a changelog, and then publishing your package!&lt;/p&gt;

&lt;p&gt;We've also added first class support for independently released projects, meaning you can now target a specific project for release with the &lt;code&gt;--projects&lt;/code&gt; command. For example, you can create a new version for just one project in your workspace with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nx release version patch --project=my-project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Angular 17 (AND NgRx 17) Support&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9jklw76upfhu8eef5q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9jklw76upfhu8eef5q8.png" alt="Angular new" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://angular.dev/"&gt;Angular&lt;/a&gt; is in the middle of &lt;a href="https://blog.angular.io/introducing-angular-v17-4d7033312e4b"&gt;a HUGE renaissance&lt;/a&gt;, between their new logo, new docs site, and introduction of some awesome features like Signals.&lt;/p&gt;

&lt;p&gt;Nx is here to support the transition! Nx has always been a great fit for Angular, and now supports Angular 17 as well as NgRx 17.&lt;/p&gt;

&lt;p&gt;To automatically migrate existing workspaces to Angular v17, run the commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nx migrate latest
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nx migrate &lt;span class="nt"&gt;--run-migrations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use the &lt;code&gt;--interactive&lt;/code&gt; flag if you want to migrate your workspace to the latest version of Nx while staying on your current version of Angular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nx migrate latest &lt;span class="nt"&gt;--interactive&lt;/span&gt;
&lt;span class="go"&gt;✔ Do you want to update to TypeScript v5.2? (Y/n) · true
✔ Do you want to update the Angular version to v17? (Y/n) · false

&lt;/span&gt;&lt;span class="gp"&gt; &amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;NX   The migrate &lt;span class="nb"&gt;command &lt;/span&gt;has run successfully.
&lt;span class="go"&gt;
   - package.json has been updated.
   - migrations.json has been generated.

&lt;/span&gt;&lt;span class="gp"&gt; &amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;NX   Next steps:
&lt;span class="go"&gt;
   - Run 'nx migrate --run-migrations'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Smart Monorepos - Fast CI&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We just gave our &lt;a href="https://nx.dev"&gt;Nx homepage&lt;/a&gt; a small facelift, including a new tagline, subtagline and illustration to better reflect Nx's mission statement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxsdzq77zhjj4skrme2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxsdzq77zhjj4skrme2v.png" alt="New HomePage!" width="800" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you enter the monorepo space, having good local development experience and tooling to support you is one thing, scaling is the other. And scaling comes with multiple challenges, from scaling teams working on the monorepo to maintaining high throughput on CI.&lt;/p&gt;

&lt;p&gt;The latter is a common headache and we've seen companies struggle. With Nx we're moving into the direction of becoming your e2e solution for monorepos, where we don't just cover your local dev experience, but also provide robust and scalable solutions on CI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnk6hlu5btscrx4052hz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnk6hlu5btscrx4052hz.png" alt="Effortless, Fast CI" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're super excited to have launched "Nx Agents" to Early Access. If you haven't seen Victor's video yet about how he reduced e2e tests from 90 minutes to 10, then make sure &lt;a href="https://nx.dev/ci/features/nx-agents"&gt;to check it out&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Nx Agents"&lt;/strong&gt; are the next iteration of DTE, providing a more flexible, cost effective and more performant approach to distribution on CI. This includes things like being able to dynamically allocate machines based on the size of the PR and flaky task detection and re-running. Also, it can be configured with a single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start CI run&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npx&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nx-cloud&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;start-ci-run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--distributes-on="8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;linux-medium-js"'&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run Nx Agents on any CI provider. If you're curious, &lt;a href="https://go.nx.dev/nx-agents-ea"&gt;sign up for early access&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  New Canary Releases&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We've added a new npm release tag: canary!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftep342gapxheetlbye5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftep342gapxheetlbye5m.png" alt="New NPM Release Tag: Canary" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This canary release is created via a &lt;a href="https://github.com/nrwl/nx/blob/master/.github/workflows/publish.yml#L5C61-L5C61"&gt;cron job&lt;/a&gt; that will regularly publish the current contents of the master branch of Nx.&lt;/p&gt;

&lt;p&gt;You can give the canary version a try new for a new workspace using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; npx create-nx-workspace@canary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be useful for previewing new not-yet released features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Release Livestream&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/OXXTUjSO1hs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We're going live in January with the Nx team to go over these updates as well! Be sure to click the link to get notified when we go live! And feel free to come with your questions in the chat!&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatically Update Nx&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Updating Nx and its plugins is easy as we ship an &lt;a href="https://nx.dev/core-features/automate-updating-dependencies"&gt;automated migration command&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx migrate latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating your dependencies, run any necessary migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx migrate --run-migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;That’s all for now folks! We’re just starting up a new iteration of development on Nx, so be sure to subscribe to our &lt;a href="https://www.youtube.com/@nxdevtools"&gt;YouTube channel&lt;/a&gt; to get updates when new features land! Until next time, KEEP WORKING HARD!&lt;/p&gt;

&lt;h3&gt;
  
  
  Learn more
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;a href="https://nx.dev"&gt;Nx Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👩‍💻 &lt;a href="https://github.com/nrwl/nx"&gt;Nx GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://go.nx.dev/community"&gt;Nx Official Discord Server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📹 &lt;a href="https://www.youtube.com/@nxdevtools"&gt;Nx Youtube Channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://nx.app"&gt;Speed up your CI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Nx Release Notes:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-17-0-has-landed-1dad19176ff3"&gt;Nx 17&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-8-release-e38e3bb503b5"&gt;Nx 16.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-5-release-7887a27cb5"&gt;Nx 16.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-is-here-69584ec87053"&gt;Nx 16.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-8-rust-hasher-nx-console-for-intellij-deno-node-and-storybook-aa2b8585772e"&gt;Nx 15.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-7-node-support-angular-lts-lockfile-pruning-46f067090711"&gt;Nx 15.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-4-vite-4-support-a-new-nx-watch-command-and-more-77cbf6c9a711"&gt;Nx 15.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-3-standalone-projects-vite-task-graph-and-more-3ed23f7827ed"&gt;Nx 15.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nx</category>
      <category>javascript</category>
      <category>npm</category>
      <category>rust</category>
    </item>
    <item>
      <title>Nx 17.0 Has Landed!!!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Tue, 24 Oct 2023 14:46:00 +0000</pubDate>
      <link>https://dev.to/nx/nx-170-has-landed-p60</link>
      <guid>https://dev.to/nx/nx-170-has-landed-p60</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/1Z0iA9K1o8M"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We're excited to announce the release of Nx version 17!&lt;/p&gt;

&lt;p&gt;In this article, we'll cover the main things you need to know to get the most of Nx 17!&lt;/p&gt;

&lt;p&gt;Here's a Table of Contents so you can skip straight to the updates you care about the most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a Vue-tiful Day for Nx&lt;/li&gt;
&lt;li&gt;Enhancements to Module Federation Support&lt;/li&gt;
&lt;li&gt;More Consistent Generator Paths&lt;/li&gt;
&lt;li&gt;The NEW Nx AI Chatbot&lt;/li&gt;
&lt;li&gt;More Seamless Integration With Nx Cloud&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nx.json&lt;/code&gt; Simplification&lt;/li&gt;
&lt;li&gt;Nx Repo Begins Dog-Fooding Nx Workflows&lt;/li&gt;
&lt;li&gt;Task Graphing Improvements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@nx/linter&lt;/code&gt; Renamed to &lt;code&gt;@nx/eslint&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New Experimental Feature: Nx Release&lt;/li&gt;
&lt;li&gt;Experimental: Nx Project Inference API v2&lt;/li&gt;
&lt;li&gt;20k Github Stars!!&lt;/li&gt;
&lt;li&gt;How to Update Nx&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It's a Vue-tiful Day for Nx! &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Ever since we added Vite a first-class citizen to Nx workspaces (see &lt;code&gt;@nx/vite&lt;/code&gt;) we started falling in love with the Vue commmunity. The only logical next step: Nx now provides a brand new Vue plugin that is being maintained by the Nx team! (And we're already working on a &lt;a href="https://nuxt.com/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; plugin 🤫)&lt;/p&gt;

&lt;p&gt;The first place you might notice this new support is in the &lt;code&gt;create-nx-workspace&lt;/code&gt; script:&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%2Fuploads%2Farticles%2Frrt53f1l4ebxsmnm5huq.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%2Fuploads%2Farticles%2Frrt53f1l4ebxsmnm5huq.png" alt="Vue in create-nx-workspace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting this option will create a brand new Nx workspace with a fresh new Vue application, all setup and ready to develop! To add new Vue projects to your existing Nx workspaces, just add our new &lt;code&gt;@nx/vue&lt;/code&gt; package as a dev dependency to your workspace, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm add -D @nx/vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you'll have access to Nx generators so that you can generate Vue applications, libraries, components, and more in your workspace:&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%2Fuploads%2Farticles%2Fl5v7m6p11ynhqssy4r2g.gif" 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%2Fuploads%2Farticles%2Fl5v7m6p11ynhqssy4r2g.gif" alt="Accessing @nx/vue generators via Nx Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're very excited for this support to land and we're eager to get it into our user's hands and see what Nx can do to help Vue developers so we can continue to refine our support and make Vue with Nx an excellent developer experience.&lt;/p&gt;

&lt;p&gt;If you're eager to learn more, make sure to &lt;a href="https://nx.dev/getting-started/tutorials/vue-standalone-tutorial" rel="noopener noreferrer"&gt;check out our new Vue standalone tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancements to Module Federation Support &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx already had really great support for Module Federation - Nx 17 improves on this support:&lt;/p&gt;

&lt;h3&gt;
  
  
  NEW: Typesafe Config
&lt;/h3&gt;

&lt;p&gt;Projects created with Nx's generators for module federation will now be created with a &lt;code&gt;module-federation.config.ts&lt;/code&gt; file (as opposed to a &lt;code&gt;.js&lt;/code&gt; file). A new &lt;code&gt;ModuleFederationConfig&lt;/code&gt; interface is now exported from the &lt;code&gt;@nx/webpack&lt;/code&gt; plugin as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Typesafety Across Modules
&lt;/h3&gt;

&lt;p&gt;Nx 17 improves typesafety across modules now so that proper typesafety is now supported across dynamic imports.&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%2Fi.imgur.com%2FcUWkV4U.gif" 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%2Fi.imgur.com%2FcUWkV4U.gif" alt="Federated Typesafety"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NEW GENERATOR: Federate ANYTHING.
&lt;/h3&gt;

&lt;p&gt;Both the &lt;code&gt;@nx/react&lt;/code&gt; and &lt;code&gt;@nx/angular&lt;/code&gt; now include a &lt;code&gt;federate-module&lt;/code&gt; generator. This will allow you to create a federated module out of any Nx project.&lt;/p&gt;

&lt;p&gt;To run this generator, use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; nx g federate-module &amp;lt;project name&amp;gt; --path=&amp;lt;path to module to be exposed&amp;gt; --remote=&amp;lt;name of remote exposing module&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new project that exposes the targeted project as a federated module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Module Versions
&lt;/h3&gt;

&lt;p&gt;Nx now supports targeted versioning for federated modules.&lt;/p&gt;

&lt;p&gt;To create versions for a given project, you can use the &lt;code&gt;version&lt;/code&gt; property of that project's &lt;code&gt;package.json&lt;/code&gt; file, and then &lt;code&gt;build&lt;/code&gt; that project create the version locally.&lt;/p&gt;

&lt;p&gt;Then when consuming this library, you can use the &lt;code&gt;shared&lt;/code&gt; method of the &lt;code&gt;ModuleFederationConfig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ModuleFederationConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nx/webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModuleFederationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-remote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apps/my-remote/src/app/remote-entry/entry.module.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;remotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;federated-is-odd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;libName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;libName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is-odd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;strictVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;requiredVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config will make sure that version &lt;code&gt;0.0.1&lt;/code&gt; of the &lt;code&gt;is-odd&lt;/code&gt; is used.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Consistent Generator Paths &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://youtu.be/bw8pRh0iC4A?si=lLRSSHmQ0V7agGo8&amp;amp;t=14" rel="noopener noreferrer"&gt;Similar to the adjustments we made in 16.8&lt;/a&gt; for most of our project-creating generators, v17 brings updates to how component generators work. The goal of these updates is to give you more control over the name and file location of your components.&lt;/p&gt;

&lt;p&gt;Towards this end, we've added a new &lt;code&gt;--nameAndDirectoryFormat&lt;/code&gt; option, that you can set to either &lt;code&gt;as-provided&lt;/code&gt; or &lt;code&gt;derived&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When set to &lt;code&gt;as-provided&lt;/code&gt;, the generator will use the &lt;code&gt;name&lt;/code&gt; option for the name of your component, and the &lt;code&gt;directory&lt;/code&gt; option to determine where on your file system to add the component. &lt;code&gt;as-provided&lt;/code&gt; will be used by default if none is specified.&lt;/p&gt;

&lt;p&gt;When set to &lt;code&gt;derived&lt;/code&gt;, the generator will try to determine where to create your component based on the &lt;code&gt;project&lt;/code&gt; option - which will mostly operate how component generators do now.&lt;/p&gt;

&lt;p&gt;In addition, component generators now follow any given casing for component files. For example, let's say that we have an integrated monorepo with a react application called "my-app", and we want to add a "Home" component. With Nx 17, you can run the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nx g component Home &lt;span class="nt"&gt;--directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;apps/my-app/src/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;code&gt;Home.tsx&lt;/code&gt; file will be added in the &lt;code&gt;apps/my-app/src/app&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;You can now also build your directory path right into generator command. For example, the same "Home" component would be created via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nx g component apps/my-app/src/app/Home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, generators will now factory in your current working directory, so you can also create this "Home" component via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;apps/my-app/src/app
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nx g component Home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The NEW Nx AI ChatBot &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We've added a new AI ChatBot to our docs site. You can access it now at &lt;a href="https://nx.dev/ai-chat" rel="noopener noreferrer"&gt;https://nx.dev/ai-chat&lt;/a&gt;&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%2Fi.imgur.com%2FdQMiJSo.gif" 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%2Fi.imgur.com%2FdQMiJSo.gif" alt="Demonstrating the Nx Ai Chatbot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature is still in beta, so please use the thumbs up/thumbs down buttons to provide feedback on whether the chat bot is accurate and helpful!&lt;/p&gt;

&lt;h2&gt;
  
  
  More Seamless Integration With Nx Cloud &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;After running the &lt;code&gt;nx migrate&lt;/code&gt; command to upgrade to Nx 17 and using Nx Cloud, you may have observed the removal of &lt;code&gt;nx-cloud&lt;/code&gt; from your dev dependencies. Don't worry - Nx Cloud is not only still around but thriving. Instead of having a standalone package, we've integrated the Nx Cloud communication layer directly into the &lt;code&gt;nx&lt;/code&gt; package. This ensures a seamless connection whenever you opt-in and eliminates potential version misalignment concerns with our API endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;nx.json&lt;/code&gt; Simplification &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Our Nx Cloud updates come alongside some other configuration changes in your &lt;code&gt;nx.json&lt;/code&gt; file and project configuration as well.&lt;/p&gt;

&lt;p&gt;Specifically, we've added an optional &lt;code&gt;nxCloudAccessToken&lt;/code&gt; to the &lt;code&gt;nx.json&lt;/code&gt; file - as long as a token is provided here, we'll make sure that you take advantage of the currently deployed version of Nx Cloud when running commands with Nx.&lt;/p&gt;

&lt;p&gt;We've also removed the need to specify &lt;code&gt;cacheableOperations&lt;/code&gt; at the task-runner level. From now on, every &lt;code&gt;target&lt;/code&gt; configured in your &lt;code&gt;project.json&lt;/code&gt; can be specified as cacheable using the &lt;code&gt;cache&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nx.json&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;"targetDefaults"&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;"build"&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;"cache"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you used the &lt;code&gt;nx migrate&lt;/code&gt; command, all updates will be handled for you, using the &lt;code&gt;targetDefaults&lt;/code&gt; in your &lt;code&gt;nx.json&lt;/code&gt; file. More &lt;a href="https://nx.dev/core-features/cache-task-results" rel="noopener noreferrer"&gt;on the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In general, we've been working hard at reducing and simplifying all the configuration that is required for your Nx workspaces. Checkout our latest guide on how to &lt;a href="https://nx.dev/recipes/running-tasks/reduce-repetitive-configuration" rel="noopener noreferrer"&gt;Reduce Repetitive Configuration&lt;/a&gt; for more, and stay tuned as we've got new efforts underway to make this simplification even more appealing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Repo Dog-Fooding Nx Workflows &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;At Nx Conf in New York City, we unvieled the next big step for Nx: &lt;strong&gt;Nx Workflows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you missed it, Simon Critchley walks you through &lt;a href="https://dev.to/nx/nx-conf-2023-recap-53ep"&gt;in his Nx Conf talk&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/JG1FWfZFByM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Put simply, Nx Workflows represents Nx entering the CI provider arena, where Nx can now provide you with Cloud Computation to run your CI tasks. This creates the foundation for a whole new class of Nx Cloud features that we're very excited to be working on in the coming cycles. &lt;/p&gt;

&lt;p&gt;The Nx repo itself is now "dog-fooding" this latest feature (dog-fooding refers to using our tool in our own projects, or "eating our own dog food"), and you can &lt;a href="https://staging.nx.app/orgs/62d013d4d26f260059f7765e/workspaces/62d013ea0852fe0a2df74438/overview" rel="noopener noreferrer"&gt;see how it's going now on our public Nx Cloud workspace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://staging.nx.app/orgs/62d013d4d26f260059f7765e/workspaces/62d013ea0852fe0a2df74438/overview" rel="noopener noreferrer"&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%2Fuploads%2Farticles%2Fbb8gtivmfhm63rk6kr1e.png" alt="Public Nx Workflows For Nx Repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nx Workflows are still in the experimental phase, we're running pilots with our enterprise clients and we're excited to open this up for everyone soon!&lt;/p&gt;

&lt;h2&gt;
  
  
  Task Graphing Improvements &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Task Caching in Nx is based on a set of "input" files that are calculated for a given task. You can specify certain files or patterns of files in your &lt;code&gt;project.json&lt;/code&gt; for a specific task or in the &lt;code&gt;targetDefaults&lt;/code&gt; of your &lt;code&gt;nx.json&lt;/code&gt; to set the default file sets for your inputs.&lt;/p&gt;

&lt;p&gt;In the past, there's been some difficulties in determining specifically which files were included and which weren't for a given task. This is where our latest update to the task graph comes in:&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%2Fhackmd.io%2F_uploads%2FBygrZ7VG6.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%2Fhackmd.io%2F_uploads%2FBygrZ7VG6.png" alt="Task Graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can open this graph using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; nx graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then selecting "Task" from the "Project"/"Task" graph dropdown in the top left. Clicking on a specific task now allows you to see a comprehensive list of all files that were factored in as inputs for this task:&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%2Fhackmd.io%2F_uploads%2FHyeH-XVf6.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%2Fhackmd.io%2F_uploads%2FHyeH-XVf6.png" alt="Task Graph with Inputs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;@nx/linter&lt;/code&gt; Renamed to &lt;code&gt;@nx/eslint&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;After running &lt;code&gt;nx migrate&lt;/code&gt;, you may have noticed that the &lt;code&gt;@nx/linter&lt;/code&gt; plugin was removed and replaced with &lt;a href="https://nx.dev/nx-api/eslint" rel="noopener noreferrer"&gt;&lt;code&gt;@nx/eslint&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Nx 17, we removed any remaining traces of &lt;code&gt;tslint&lt;/code&gt; from our linter package, so this is mainly a simple rename to more accurately describe the package (and to remove any confusion that this package is intended to support linting for other languages/platforms).&lt;/p&gt;

&lt;h2&gt;
  
  
  New Experimental Feature: Nx Release &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nx release&lt;/code&gt; is a new top level command on the Nx CLI which is designed to help you with versioning, changelog generation, and publishing of your projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nx release version&lt;/code&gt; - Determine and apply version updates to projects and their dependents&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nx release changelog&lt;/code&gt; - Generate CHANGELOG.md files and optional Github releases based on git commits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nx release publish&lt;/code&gt; - Take the freshly versioned projects and publish them to a remote registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;nx release&lt;/code&gt; is still experiment and therefore subject to change, but the Nx repo itself is now using these commands to version itself, as well as generate changelogs, &lt;a href="https://github.com/nrwl/nx/releases" rel="noopener noreferrer"&gt;Github releases&lt;/a&gt;, and publish our packages to npm.&lt;/p&gt;

&lt;p&gt;As we solidify this command, we intend to bring robust support for various versioning and publishing strategies, as well as built-in support for publishing packages or modules to a variety of languages, registries, and platforms.&lt;/p&gt;

&lt;p&gt;For more &lt;a href="https://nx.dev/nx-api/nx/documents/release" rel="noopener noreferrer"&gt;checkout our API docs&lt;/a&gt;, and be sure to catch James Henry's announcement of this new command at &lt;a href="https://dev.to/nx/nx-conf-2023-recap-53ep"&gt;Nx Conf&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/p5qW5-2nKqI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimental: Nx Project Inference API v2 &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;At Nx, we're OBSESSED with building a better, more robust experience for our developers. Towards this end, we're now in &lt;a href="https://nx.dev/extending-nx/recipes/project-graph-plugins" rel="noopener noreferrer"&gt;v2 of our Project Inference API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This API is a way of extending the Nx project graph, which can be particularly helpful for extending Nx to support other languages, allowing Nx to determine where to find and draw boundaries around projects in your workspace. A great example is our very own &lt;a href="https://nx.dev/getting-started/tutorials/vue-standalone-tutorial" rel="noopener noreferrer"&gt;Vue plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Interestingly, v2 includes support for dynamic targets as well. This opens up exciting new doors to reducing configuration, and we hope to expand on this to better support our first-party plugins in the near future.&lt;/p&gt;

&lt;p&gt;For most developers, the main thing you need to know is that plugins may now add additional targets that you won't see in your &lt;code&gt;project.json&lt;/code&gt; file. To see your actual project configuration, you can now use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nx show project &amp;lt;project_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For plugin authors, check out the &lt;a href="https://nx.dev/extending-nx/recipes/project-graph-plugins" rel="noopener noreferrer"&gt;v2 documentation&lt;/a&gt; to see how you can take advantage of the new API to deliver a better experience to your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  20k Github Stars!! &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx is SOOO CLOSE to 20,000 stars on github! If Nx has been helpful to you, &lt;a href="https://github.com/nrwl/nx" rel="noopener noreferrer"&gt;please help us get there!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nrwl/nx" rel="noopener noreferrer"&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%2Fuploads%2Farticles%2Fl2lw01qq7sow5thbarn5.png" alt="Star us on Github!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Update Nx &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx is known to &lt;a href="https://nx.dev/core-features/automate-updating-dependencies" rel="noopener noreferrer"&gt;help you automatically migrate&lt;/a&gt; to the new version (including potentially breaking changes). To update simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx migrate latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will update your Nx workspace dependencies, configuration and code to the latest version. After updating your dependencies, run any necessary migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx migrate --run-migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That’s all for now folks! We’re just starting up a new iteration of development on Nx, so be sure to subscribe to our &lt;a href="https://www.youtube.com/@nxdevtools" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt; to get updates when new features land! Until next time, KEEP WORKING HARD!&lt;/p&gt;

&lt;h3&gt;
  
  
  Learn more
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;a href="https://nx.dev" rel="noopener noreferrer"&gt;Nx Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👩‍💻 &lt;a href="https://github.com/nrwl/nx" rel="noopener noreferrer"&gt;Nx GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://go.nx.dev/community" rel="noopener noreferrer"&gt;Nx Official Discord Server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📹 &lt;a href="https://www.youtube.com/@nxdevtools" rel="noopener noreferrer"&gt;Nx Youtube Channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://nx.app" rel="noopener noreferrer"&gt;Speed up your CI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Nx Release Notes:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-8-release-e38e3bb503b5" rel="noopener noreferrer"&gt;Nx 16.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-5-release-7887a27cb5" rel="noopener noreferrer"&gt;Nx 16.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-is-here-69584ec87053" rel="noopener noreferrer"&gt;Nx 16.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-8-rust-hasher-nx-console-for-intellij-deno-node-and-storybook-aa2b8585772e" rel="noopener noreferrer"&gt;Nx 15.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-7-node-support-angular-lts-lockfile-pruning-46f067090711" rel="noopener noreferrer"&gt;Nx 15.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-4-vite-4-support-a-new-nx-watch-command-and-more-77cbf6c9a711" rel="noopener noreferrer"&gt;Nx 15.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-3-standalone-projects-vite-task-graph-and-more-3ed23f7827ed" rel="noopener noreferrer"&gt;Nx 15.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue</category>
      <category>nx</category>
      <category>eslint</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Nx 16.8 Release!!!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Wed, 06 Sep 2023 14:34:49 +0000</pubDate>
      <link>https://dev.to/nx/nx-168-release-mn5</link>
      <guid>https://dev.to/nx/nx-168-release-mn5</guid>
      <description>&lt;p&gt;As always, the Nx Team has been hard at work, and we're here to launch our latest release: Nx 16.8! And it's absolutely bursting with new features. We'll go into all these in depth below, but be sure to check out our latest release video on YouTube!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/bw8pRh0iC4A"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you have more questions - be sure to stop by our latest Nx Live to ask them there! You can sign up for a notification from the link below!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wj-gb6jqyzA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Community News!
&lt;/h2&gt;

&lt;p&gt;Before we jump into the features, be sure to get plugged in to get the most out of Nx in &lt;a href="https://discord.gg/Pzz9BW62gD"&gt;our NEW discord server&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Be sure to checkout our livestream with Jay Bell (Nx Champion, former Nx Community Slack admin, and current Nx Discord Admin) all about the news:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/yBsXB8xuTog"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Nx is also coming up on 20k Github stars &lt;a href="https://github.com/nrwl/nx"&gt;please help us get there!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nrwl/nx"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HV2Om7M4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/Bk_MtBHRn.png" alt="Closing in on 20k stars!" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW PROJECT GENERATOR BEHAVIOR: Project Creation Paths and Names in Nx Generators
&lt;/h2&gt;

&lt;p&gt;Alright, getting into the newest updates from Nx, the biggest change is coming in the form of Nx project generators.&lt;/p&gt;

&lt;p&gt;Originally, all Nx workspaces had a directory structure where applications were always held in an &lt;code&gt;apps/&lt;/code&gt; directory, while libraries were held in the &lt;code&gt;libs/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Over the years, we found this to be much too rigid, so we made these locations more configurable. We added a &lt;code&gt;workspaceLayout&lt;/code&gt; category to the &lt;code&gt;nx.json&lt;/code&gt; file, where you could set &lt;code&gt;appDir&lt;/code&gt; and &lt;code&gt;libDir&lt;/code&gt; options to customize the names of these directories.&lt;/p&gt;

&lt;p&gt;We also simplified the definition of Nx projects to be any directory that had a &lt;code&gt;project.json&lt;/code&gt; file in it, allowing for the ability to nest projects in your filesytem and also forego app or lib directories altogether!&lt;/p&gt;

&lt;p&gt;With Nx 16.8, we're introducing an all-new option in this &lt;code&gt;workspaceLayout&lt;/code&gt; category: &lt;code&gt;projectNameAndRootFormat&lt;/code&gt; where you can chose either the option to apply project names &lt;code&gt;as-provided&lt;/code&gt; or &lt;code&gt;derived&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wE9P5KKV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/rkeJVdISRn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wE9P5KKV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/rkeJVdISRn.png" alt="projectNameAndRootFormat options" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't set this option in your &lt;code&gt;nx.json&lt;/code&gt; file, our generators will now prompt you as to how we should set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the name of your project (as in the project's &lt;code&gt;project.json&lt;/code&gt; file)&lt;/li&gt;
&lt;li&gt;where to put the project in your filesystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JTNBmZRb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/Sy6LtUSR2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JTNBmZRb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/Sy6LtUSR2.png" alt="as provided vs. derived" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can avoid the extra prompt above by setting the &lt;code&gt;projectNameAndRootFormat&lt;/code&gt; in the &lt;code&gt;workspaceLayout&lt;/code&gt; category of your &lt;code&gt;nx.json&lt;/code&gt; file, or by providing it in in your generate command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nx g app my-app &lt;span class="nt"&gt;--projectNameAndRootFormat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;as-provided
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will change some of the default behaviors of our generators and for new Nx workspaces. By default, new workspaces will be set to &lt;code&gt;as-provided&lt;/code&gt;, so longtime Nx users might notice that if they create a new workspace and start generating apps or libs, that they will now get generated at the root of the workspace instead of inside the &lt;code&gt;apps/&lt;/code&gt; or &lt;code&gt;libs/&lt;/code&gt; directory like they used to.&lt;/p&gt;

&lt;p&gt;To get the legacy behavior, be sure to change the &lt;code&gt;projectNameAndRootFormat&lt;/code&gt; to &lt;code&gt;derived&lt;/code&gt; and/or use the &lt;code&gt;name&lt;/code&gt; option to set the name of the project, and the &lt;code&gt;directory&lt;/code&gt; option to set the full path of the target directory for your new projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; nx g app --name=my-app --directory=apps/my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Packaging TypeScript Libraries - Secondary Entrypoints, ESM, CJS
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Vy4d0-SF5cY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Nx has always had first-class TypeScript support from the very beginning. You never really had to worry about Type Definition files being properly included or setting up TS support in the first place. But there are some aspects that can be tricky, like defining and properly exporting secondary entry points or packaging in multiple formats (ESM and CJS). That's why we did some research to help make it easier.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt; format supports an &lt;a href="https://nodejs.org/api/packages.html#package-entry-points"&gt;export field&lt;/a&gt; which is a modern alternative to the &lt;code&gt;main&lt;/code&gt; property, allowing to have multiple such entry points.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&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;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./package.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./foo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/foo.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"./bar"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/bar.js"&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="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;To make it easier to maintain these, we've added two new options to the &lt;code&gt;@nx/js&lt;/code&gt; package: &lt;code&gt;additionalEntryPoints&lt;/code&gt; and &lt;code&gt;generateExportsField&lt;/code&gt;. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/my-awesome-lib/project.json&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-awesome-lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targets"&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;"build"&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;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@nx/js:tsc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"packages/my-awesome-lib/src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"additionalEntryPoints"&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;"packages/my-awesome-lib/src/foo.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"generateExportsField"&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="w"&gt;
      &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="c1"&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="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;When building the library, the &lt;code&gt;@nx/js:tsc&lt;/code&gt; executor automatically adds the correct &lt;code&gt;exports&lt;/code&gt; definition to the resulting &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This makes it easy to maintain the &lt;code&gt;exports&lt;/code&gt; field, something that becomes even more relevant if we &lt;strong&gt;want to add add multiple formats&lt;/strong&gt;. By switching to the &lt;code&gt;@nx/rollup:rollup&lt;/code&gt; executor, we can also add the &lt;code&gt;format&lt;/code&gt; option and define &lt;code&gt;esm&lt;/code&gt; and &lt;code&gt;cjs&lt;/code&gt; as the target formats we want to have. Note we still keep the &lt;code&gt;additionalEntryPoints&lt;/code&gt; and &lt;code&gt;generateExportsField&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/my-awesome-lib/project.json&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-awesome-lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"targets"&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;"build"&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;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@nx/rollup:rollup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"packages/my-awesome-lib/src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"format"&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;"esm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cjs"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"additionalEntryPoints"&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;"packages/my-awesome-lib/src/foo.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"generateExportsField"&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="w"&gt;
      &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="c1"&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="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;After compiling our package using &lt;code&gt;nx build my-awesome-lib&lt;/code&gt; we'll get the following output in our &lt;code&gt;dist&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-awesome-lib
└─ .
   ├─ README.md
   ├─ foo.cjs.d.ts
   ├─ foo.cjs.js
   ├─ foo.esm.js
   ├─ index.cjs.d.ts
   ├─ index.cjs.js
   ├─ index.esm.js
   ├─ package.json
   └─ src
      ├─ foo.d.ts
      ├─ index.d.ts
      └─ lib
         └─ my-awesome-lib.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our &lt;code&gt;package.json&lt;/code&gt; will look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "my-awesome-lib",
  "version": "0.0.1",
  ...
  "type": "commonjs",
  "main": "./index.cjs.js",
  "typings": "./src/index.d.ts",
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "import": "./index.esm.js",
      "default": "./index.cjs.js"
    },
    "./foo": {
      "import": "./foo.esm.js",
      "default": "./foo.cjs.js"
    }
  },
  "module": "./index.esm.js"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  High Performance TypeScript Compilation: Introducing Batch Mode &amp;amp; Rust-based Dependency Resolution
&lt;/h2&gt;

&lt;p&gt;Speed is in our DNA! And TypeScript compilation can be slow which is mostly due to &lt;code&gt;tsc&lt;/code&gt; being a costly operation. You feel this in particular in a monorepo where you have multiple projects that are being compiled independently, thus launching dozens of processes of the &lt;code&gt;tsc&lt;/code&gt; compiler.&lt;/p&gt;

&lt;p&gt;To help with this, the TypeScript team introduced &lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html"&gt;Project References&lt;/a&gt; which performs incremental compilation by caching intermediate results. However this comes with some &lt;a href="https://www.typescriptlang.org/docs/handbook/project-references.html#caveats-for-project-references"&gt;caveats you need to be aware of&lt;/a&gt; and it can be intense to maintain by hand on a large codebase.&lt;/p&gt;

&lt;p&gt;We wanted to make it transparent though and not something the user needs to worry about. That's why we introduced &lt;a href="https://nx.dev/showcase/benchmarks/tsc-batch-mode"&gt;"Batch Mode"&lt;/a&gt;. Batch mode is still experimental, and you can enable it for any project using the &lt;code&gt;@nx/js:tsc&lt;/code&gt; executor by adding the environment variable &lt;code&gt;NX_BATCH_MODE=true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;NX_BATCH_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;nx run-many &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When enabling batch mode, Nx leverages the underlying &lt;a href="https://nx.dev/core-features/explore-graph"&gt;project graph&lt;/a&gt; to generate TypeScript project references behind the scenes for you, to fully leverage TS incremental building. The results are amazing. According to &lt;a href="https://github.com/nrwl/large-ts-monorepo"&gt;our benchmarks&lt;/a&gt;, batch mode has the potential to speed up Typescript compilation by up to 5x for large monorepos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nx.dev/showcase/benchmarks/tsc-batch-mode"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ec1SA7rQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://nx.dev/documentation/shared/images/benchmarks/ts-benchmark.gif" alt="Typescript Batchmode Support" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to batch mode, the processing of Typescript dependencies is &lt;strong&gt;now using a faster and more accurate Rust implementation&lt;/strong&gt;. This should be transparent to most users - aside from your monorepo becoming even faster!&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW Nx PLUGIN: Playwright
&lt;/h2&gt;

&lt;p&gt;A new Nx plugin has appeared!!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nx.dev/packages/playwright"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OPv4BIcW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/rknho8HCn.png" alt="New Nx Plugin: Playwright" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nx now supports Playwright as an e2e option alongside our long-standing support for Cypress!&lt;/p&gt;

&lt;p&gt;First off, this means we've added Playwright as an option when generating new frontend applications:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qYE-6cdt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/H14YaIS0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qYE-6cdt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/H14YaIS0h.png" alt="e2e options" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to add playwright to an existing project in your workspace you can also do that now by installing the plugin (here using &lt;code&gt;npm&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm add -D @nx/playwright
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then running our new generator to add playwright to your project (here I'm adding it to my application called &lt;code&gt;my-app&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nx g @nx/playwright:configuration --project=my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will setup playwright for you, and add an &lt;code&gt;e2e&lt;/code&gt; target to your project using playwright!&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW INTEGRATION: Netlify's Improved Monorepo Experience
&lt;/h2&gt;

&lt;p&gt;On Netlify's enterprise tier, approximately 46% of builds are monorepos, with the majority leveraging &lt;a href="https://nx.dev"&gt;Nx&lt;/a&gt; and &lt;a href="https://lerna.js.org"&gt;Lerna&lt;/a&gt;. Recognizing this trend, Netlify has focused on enhancing the setup and deployment experiences for monorepo projects. In particular they worked on an "automatic monorepo detection" feature. When you connect your project to GitHub, Netlify automatically detects if it's part of a monorepo, reads the relevant settings, and pre-configures your project. This eliminates the need for manual setup. This feature also extends to local development via the Netlify CLI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/juristr"&gt;Juri Strumpflohner&lt;/a&gt; from the Nx team sits down with &lt;a href="https://twitter.com/luka5c0m"&gt;Lukas Holzer&lt;/a&gt; from Netlify to discuss what monorepos are, when you might need them, their challenges and benefits, and the tooling support Nx provides. But it's not just talk; they also walk you through setting up a new Nx monorepo, adding Netlify Edge functions, and deploying it all to Netlify—both via the website and locally through the newly improved Netlify CLI.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KL7PCwf-mtM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you prefer the blog post, check out &lt;a href="https://www.netlify.com/blog/elevating-enterprise-deployment-introducing-an-enhanced-monorepo-experience-on-netlify/"&gt;"Elevating enterprise deployment: Introducing an enhanced monorepo experience on Netlify"&lt;/a&gt; on the Netlify blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW Nx CONSOLE FEATURE: Interactive Graph Functionality
&lt;/h2&gt;

&lt;p&gt;If you're not familiar - Nx Console is our IDE enhancement for &lt;a href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console"&gt;VsCode (1.4 million installations!!)&lt;/a&gt; and &lt;a href="https://plugins.jetbrains.com/plugin/21060-nx-console"&gt;JetBrains Products (Intellij/WebStorm/etc - 52 thousand downloads!)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nx Console provides a Graphical User Interface for running tasks and generating code for your Nx Monorepos, and it also provides the ability to view your project graph &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dcHHDmkK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HyXdPdrRh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dcHHDmkK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HyXdPdrRh.png" alt="Nx Project Graph" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and task graph &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VrRU_Fid--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HkeYwdS03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VrRU_Fid--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HkeYwdS03.png" alt="Nx Task Graph" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;within your IDE!&lt;/p&gt;

&lt;p&gt;As of our latest release, Nx Console has added the new feature of adding buttons inside the project graph to take you directly to the selected project in the project graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CkjmN1F_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/r1SYUdSCh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CkjmN1F_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/r1SYUdSCh.png" alt="Button on Project Graph" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And gives you a "play button" to run any task within your task graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zDBeUeRn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/Bya68Or0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zDBeUeRn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/Bya68Or0n.png" alt="Button on Task Graph" width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ENHANCEMENT: Storybook Support for Interaction Testing
&lt;/h2&gt;

&lt;p&gt;Storybook 7 introduces new &lt;a href="https://storybook.js.org/docs/react/writing-tests/interaction-testing"&gt;Interaction Testing&lt;/a&gt; to their platform!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2U53FgPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HyrguOBCn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2U53FgPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HyrguOBCn.png" alt="Storybook Interaction Testing" width="790" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Nx Storybook plugin has now been enhanced with a new &lt;code&gt;interactionTests&lt;/code&gt; option on the &lt;code&gt;storybook-configuration&lt;/code&gt; generator, that will automate setting up these interaction tests for you when you use Nx to generate stories for your components!&lt;/p&gt;

&lt;p&gt;You can checkout everything about Storybook Interaction tests and our Nx support for it in this video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/SaHoUx-TUs8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Nx has always provided similar functionality via our Storybook + Cypress integrations, where you can setup cypress tests to test your storybook showcases, and while you can use interaction testing to replace this integration, we will continue to support our Storybook + Cypress integrations going forward!&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW GENERATOR: convert-to-monorepo
&lt;/h2&gt;

&lt;p&gt;Towards the start of the year, we added support for Nx standalone applications. These were designed as a way of using Nx features in a way where you didn't need to opt into a monorepo setup.&lt;/p&gt;

&lt;p&gt;Ever since we introduced these, we've had requests from the community to add a generator to convert your standalone workspace to a monorepo setup - to support repos that grew to emcompass more than just the 1 application.&lt;/p&gt;

&lt;p&gt;This is where our new &lt;a href="https://nx.dev/packages/workspace/generators/convert-to-monorepo"&gt;&lt;code&gt;convert-to-monorepo&lt;/code&gt; generator&lt;/a&gt; comes into play!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nx g convert-to-monorepo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will identify all library projects in currently in your repo and place them in the &lt;code&gt;libs/&lt;/code&gt; directory that you might expect of a typical Nx monorepo. It will also hoist your application project from the root of your workspace into the &lt;code&gt;apps/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;When all is said and done, you should be able to now add additional applications to your repo now!&lt;/p&gt;

&lt;h2&gt;
  
  
  DOCS ENHANCEMENT: Third-Party Plugin Quality Indicators
&lt;/h2&gt;

&lt;p&gt;One of the great things about Nx is the extensibility, and the wide range of plugins that are available for supporting languages, frameworks, and tools that the core Nx team does not directly support.&lt;/p&gt;

&lt;p&gt;Our docs site has hosted &lt;a href="https://nx.dev/extending-nx/registry"&gt;a registry of these plugins&lt;/a&gt; for awhile now, but we've recently added npm downloads, github stars, release dates, and compatible Nx versions to this registry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CCB_urO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/BJmY3uH0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CCB_urO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/BJmY3uH0h.png" alt="Nx Plugin Registry Example" width="367" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We expect that these will be valuable signals to users as to the quality of the plugin.&lt;/p&gt;

&lt;p&gt;We've also added the ability to sort the registry by these metrics as a way of surfacing the higher quality plugins to the top!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ezBz_5Yx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/r1gQadHRn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ezBz_5Yx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/r1gQadHRn.png" alt="Nx Registry Sorting Options" width="547" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go &lt;a href="https://nx.dev/extending-nx/registry"&gt;check it out now&lt;/a&gt; live on our docs site!&lt;/p&gt;

&lt;h2&gt;
  
  
  DOCS ENHANCEMENT: Redesigned Intro &amp;amp; Examples
&lt;/h2&gt;

&lt;p&gt;Our mission to improve Nx docs continues: this edition with some major updates to our &lt;a href="https://nx.dev/docs"&gt;Docs intro page&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We improved the messaging around Nx and more prominently emphasizing the &lt;a href="https://nx.dev/core-features"&gt;core features&lt;/a&gt; that distinguish Nx from other tools out there. We also linked a new "What is Nx" video. You should check it out 🐐&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-_4WMl-Fn0w"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The core part of the intro page now has a dedicated section for "Learning Nx", surfacing some of our Nx, Nx Cloud and Monorepo videos as well as our in-depth tutorial about monorepos and single-project Nx workspaces. And we already have more in the works, so stay tuned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3GgEW1t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/SJBIhtia2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3GgEW1t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/SJBIhtia2.png" alt="Learn Nx Examples" width="720" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're also proud of all the new examples we've added in the last months. We have a brand new &lt;a href="https://github.com/nrwl/nx-recipes"&gt;Nx Recipe&lt;/a&gt; repo with a variety of example projects that showcase Nx in combination with different technologies, even outside the JS ecosystem (e.g. with Rust, Go and .Net).&lt;/p&gt;

&lt;p&gt;Go pick the ones you're most interested in!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LbWXoaC4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/S1hLnYo6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LbWXoaC4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/S1hLnYo6n.png" alt="Pick your stack" width="720" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find all of the examples and more on our "Showcase" section: &lt;a href="https://nx.dev/showcase"&gt;https://nx.dev/showcase&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW GENERATOR: ESLint Flat Config
&lt;/h2&gt;

&lt;p&gt;ESLint has announced a new config system - nicknamed "flat config" - whose intent is to be be familiar and much simpler than the current config system. You can read more about this &lt;a href="https://eslint.org/blog/2022/08/new-config-system-part-2/"&gt;in their blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As part of our continued support for ESLint, we've introduced &lt;a href="https://nx.dev/packages/linter/generators/convert-to-flat-config"&gt;a new generator&lt;/a&gt; to convert your Nx monorepo to this new system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nx g convert-to-flat-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flat config is still experimental, so you can use this as a way of easily previewing the new config system now. Just be sure not to mix the original configuration with this new configuration system!&lt;/p&gt;

&lt;h2&gt;
  
  
  New Nx Cloud Overview Video
&lt;/h2&gt;

&lt;p&gt;We've been working hard on our premium product: Nx Cloud, and part of that has been this awesome video from our own Rares Matei on what exactly Nx Cloud is!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/NZF0ZJpgaJM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Be sure to checkout the &lt;a href="https://nx.app"&gt;Nx Cloud landing site&lt;/a&gt; for more details on all the additional features you unlock with the power of the cloud!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Conf Coming Up
&lt;/h2&gt;

&lt;p&gt;Last but definitely not least, &lt;a href="https://nx.dev/conf"&gt;Nx Conf 2023&lt;/a&gt; is fast approaching! We'll be coming to you live from New York City on September 26!&lt;/p&gt;

&lt;p&gt;Be sure to &lt;a href="https://ti.to/nx-conf/nxconf2023online"&gt;register to attend online FOR FREE&lt;/a&gt; and be sure to checkout our &lt;a href="https://nx.dev/conf#agenda"&gt;lineup of talks&lt;/a&gt; and &lt;a href="https://nx.dev/conf#speakers"&gt;speakers&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;That's all for now folks! We're just starting up a new iteration of development on Nx, so be sure to subscribe to our YouTube channel to get updates when new features land! Until next time, KEEP WORKING HARD!&lt;/p&gt;

&lt;h3&gt;
  
  
  Learn more
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;a href="https://nx.dev"&gt;Nx Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👩‍💻 &lt;a href="https://github.com/nrwl/nx"&gt;Nx GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://go.nrwl.io/join-slack"&gt;Nx Official Discord Server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📹 &lt;a href="https://www.youtube.com/@nxdevtools"&gt;Nx Youtube Channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🥚 &lt;a href="https://egghead.io/courses/scale-react-development-with-nx-4038"&gt;Free Egghead course&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://nx.app"&gt;Speed up your CI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Nx Release Notes:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-5-release-7887a27cb5"&gt;Nx 16.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-is-here-69584ec87053"&gt;Nx 16.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-8-rust-hasher-nx-console-for-intellij-deno-node-and-storybook-aa2b8585772e"&gt;Nx 15.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-7-node-support-angular-lts-lockfile-pruning-46f067090711"&gt;Nx 15.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-4-vite-4-support-a-new-nx-watch-command-and-more-77cbf6c9a711"&gt;Nx 15.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-3-standalone-projects-vite-task-graph-and-more-3ed23f7827ed"&gt;Nx 15.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>nx</category>
      <category>typescript</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Nx 16.5 Release!!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Thu, 06 Jul 2023 14:26:25 +0000</pubDate>
      <link>https://dev.to/nx/nx-165-release-2o95</link>
      <guid>https://dev.to/nx/nx-165-release-2o95</guid>
      <description>&lt;p&gt;We have launched SO MANY features since our last release blog on Nx 16.0, so we're covering the major features in this blog!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/7XLoLOc3afY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Be sure to mark your calendars for our Nx 16.5 livestream as well! We'll highlight the features you see here AND field any questions live! Follow the link to schedule a notification for when we go live:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EYgkKmYbRNI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Targetting Tasks By Tags
&lt;/h2&gt;

&lt;p&gt;Our first major feature actually comes to us from the community. Nx has supported a tags property in your project.json file for awhile now - and it's main purpose has been to be used in conjuncture with the &lt;a href="https://nx.dev/core-features/enforce-project-boundaries"&gt;Nx Module Boundary lint rule&lt;/a&gt; to define which projects in your Nx workspace can depend on what - for example, you don't want your frontend applications to depend on any backend-specific code.&lt;/p&gt;

&lt;p&gt;With this new feature, you can add the &lt;code&gt;--tag&lt;/code&gt; option to the &lt;a href="https://nx.dev/packages/nx/documents/affected"&gt;&lt;code&gt;nx affected&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://nx.dev/packages/nx/documents/run-many"&gt;&lt;code&gt;nx run-many&lt;/code&gt;&lt;/a&gt; commands to specify to Nx to only run commands for projects that match the given tags.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/enQDQmFquGU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  NextJS 13 Support
&lt;/h2&gt;

&lt;p&gt;React Server Components, the and the new NextJS app router are here, and Nx is here to support them. We've added support for the latest versions of Next - complete with generators and executors. We've also made sure that our &lt;a href="https://nx.dev/packages/next/documents/next-config-setup#-plugin"&gt;&lt;code&gt;withNx&lt;/code&gt; NextJS plugin&lt;/a&gt;, which allows you to import from your other projects in your workspace while still working with the NextJS build scripts, works both for workspaces using our executors in an Integrated Monorepo approach, as well as for those using a Package-Based approach that are simply using the &lt;code&gt;next dev&lt;/code&gt; command directly to start their dev server.&lt;/p&gt;

&lt;p&gt;There's also built-in support for the new turbopack builder option via the &lt;code&gt;--turbo&lt;/code&gt; command, for example: &lt;code&gt;nx serve webapp --turbo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We've also launched a new preset for &lt;code&gt;npx create-nx-workpace&lt;/code&gt; for a standalone NextJS app. Checkout it out using the command: &lt;code&gt;npx create-nx-workspace@latest --preset=nextjs-standalone&lt;/code&gt; or use the interactive prompts to find it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P9YMFbE_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/H1uXmR1F3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P9YMFbE_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/H1uXmR1F3.png" alt="Using interactive prompts to create a nextjs standalone repo." width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've added a couple videos specifically on Next development in an Nx workspace in the past month, so if you're looking for more on Next, be sure to check them out!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RupxGAQ3fBY"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/X3WfXAZZH7s"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  New Nx Recipes!!!
&lt;/h2&gt;

&lt;p&gt;Did you know that we maintain &lt;a href="https://github.com/nrwl/nx-recipes"&gt;a set of Nx workspaces&lt;/a&gt; designed to serve as examples for different Nx Workspaces?&lt;/p&gt;

&lt;p&gt;We've recently added examples repos for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fastify + mongo&lt;/li&gt;
&lt;li&gt;fastify + postgres&lt;/li&gt;
&lt;li&gt;fastify + redis&lt;/li&gt;
&lt;li&gt;nextjs + trpc&lt;/li&gt;
&lt;li&gt;remix&lt;/li&gt;
&lt;li&gt;serverless + fastify + planetscale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go check it out at &lt;a href="https://github.com/nrwl/nx-recipes"&gt;https://github.com/nrwl/nx-recipes&lt;/a&gt; and let us know in the comments if there are more recipes you'd like to see or if you want to see videos made from any of the existing recipes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular 16 Support And Migrations
&lt;/h2&gt;

&lt;p&gt;Angular is continuing their pattern of releasing new and exciting features - and in Nx 16.1, we added support for Angular 16, including updates to our NgRx generators and cypress support.&lt;/p&gt;

&lt;p&gt;As usual, we provide migrations to the most recent Angular version to cover your codebase for any breaking changes going to Angular 16.&lt;/p&gt;

&lt;p&gt;And in case you missed it, Nx is no longer tied to your Angular version - the most recent version of Nx will now always &lt;a href="https://nx.dev/packages/angular/documents/angular-nx-version-matrix"&gt;support all currently LTS versions of Angular&lt;/a&gt;, meaning you DON'T have to upgrade your Angular version in order to get all these latest Nx Features. Be sure to use the &lt;code&gt;--interactive&lt;/code&gt; flag to take advantage of this feature: &lt;code&gt;nx migrate latest --interactive&lt;/code&gt;. You can find more details in &lt;a href="https://nx.dev/recipes/other/advanced-update#choosing-optional-package-updates-to-apply"&gt;our docs for choosing optional packages to apply&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/AQV4WFldwlY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  New &lt;code&gt;verdaccio&lt;/code&gt; support!
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@nx/js&lt;/code&gt; package now includes a new &lt;code&gt;setup-verdaccio&lt;/code&gt; generator as well as a &lt;code&gt;verdaccio&lt;/code&gt; executor!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/t1c925TzrzE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;nx g setup-verdaccio&lt;/code&gt; will now add a new &lt;code&gt;local-registry&lt;/code&gt; target to your workspace, that will use the new &lt;code&gt;verdaccio&lt;/code&gt; executor.&lt;/p&gt;

&lt;p&gt;To start your local registry, run the command &lt;code&gt;nx local-registry&lt;/code&gt; in one terminal, and then you can publish via commands like &lt;code&gt;npm publish&lt;/code&gt; as you normally would, but rather than publish packages to the npm registry, this will only publish those packages to your locally-running registry!&lt;/p&gt;

&lt;p&gt;While the &lt;code&gt;local-registry&lt;/code&gt; process is running, you will also be able to install packages published to your local registry as well. &lt;/p&gt;

&lt;p&gt;These tools should help in particular with local testing of publishing packages to help support  both manual and automated testing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Create your own CLI with Nx
&lt;/h2&gt;

&lt;p&gt;The use of command-line interfaces (CLIs) to create new workspaces is common in various technologies. React has one, so does Angular, Vue, Vite and many more. Maintaining a good CLI experience can be tedious though, especially because framework authors would rather want to focus on the library or framework itself.&lt;/p&gt;

&lt;p&gt;For a while now, Nx plugin authors had the possibility to create a so-called "preset" to control the entire Nx workspace structure. Once published it can be invoked by using the &lt;code&gt;--preset&lt;/code&gt; flag:&lt;code&gt;npx create-nx-workspace myapp --preset=@my/plugin&lt;/code&gt;. &lt;a href="https://github.com/qwikifiers/qwik-nx"&gt;Qwik-Nx&lt;/a&gt; is an example of that.&lt;/p&gt;

&lt;p&gt;However, many authors would rather want to have a more "branded" experience. Like being able to invoke the previous example as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-qwik-nx myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fulfill this need, in 16.5 we ship with a new &lt;code&gt;create-package&lt;/code&gt; generator that allows you to add and ship such "CLI package" with your Nx plugin.&lt;/p&gt;

&lt;p&gt;You can either create a new Nx plugin workspace immediately with a CLI package using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-nx-plugin my-own-cli --create-package-name=create-my-own-cli-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or alternatively add it to an existing Nx plugin workspace using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nx g @nx/plugin:create-package &amp;lt;cli name&amp;gt; --project=&amp;lt;existing plugin name&amp;gt; --e2eProject e2e
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  New &lt;code&gt;externalDependencies&lt;/code&gt; Input Type
&lt;/h2&gt;

&lt;p&gt;Nx now supports a new input type: &lt;code&gt;externalDependencies&lt;/code&gt; to add to the existing input types: &lt;code&gt;filesets&lt;/code&gt;, &lt;code&gt;runtime&lt;/code&gt; inputs, and &lt;code&gt;env&lt;/code&gt; variables.&lt;/p&gt;

&lt;p&gt;By default, Nx will take the defensive stance of assuming that all packages will affect your commands, meaning all external dependencies are taken into account when hashing your task's dependencies.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;externalDependencies&lt;/code&gt; input type allows you to specify a specific set of external dependencies for a given command, so you can configure your tasks to more accurately reflect their inputs.&lt;/p&gt;

&lt;p&gt;For example, if you have a &lt;code&gt;publish-package&lt;/code&gt; target that is using a &lt;code&gt;command&lt;/code&gt; of &lt;code&gt;lerna publish&lt;/code&gt; for your publishing, you can specify that the only external dependency for that specific input is &lt;code&gt;lerna&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mylib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&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;"targets"&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;"publish-package"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lerna publish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&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;"externalDependencies"&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;"lerna"&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="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="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;This way you are free to change other dependencies that don't affect this task without invalidating your cached run of the task.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/FRqgWBmHmAU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  New &lt;code&gt;@nx/dependency-checks&lt;/code&gt; EsLint Rule
&lt;/h2&gt;

&lt;p&gt;We've added a new &lt;code&gt;@nx/dependency-checks&lt;/code&gt; lint rule to help with detecting any issues when specifying dependencies for your packages.&lt;/p&gt;

&lt;p&gt;This rule will analyze your source code to determine any dependencies you are using in this specific package, and will catch any missing dependencies, mismatched versions, and unused dependencies in your project's &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Even better, this rule supports the &lt;code&gt;--fix&lt;/code&gt; option to automatically fix any issues that are found - making it a great tool for automating your dependencies.&lt;/p&gt;

&lt;p&gt;After migrating your Nx workspace, you should have access to this lint rule. To turn it on, you'll need to adjust the &lt;code&gt;lint&lt;/code&gt; targets in your &lt;code&gt;project.json&lt;/code&gt; files to include your &lt;code&gt;package.json&lt;/code&gt; files like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&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;"targets"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&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;"lint"&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;"executor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@nx/linter:eslint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&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="s2"&gt;"{options.outputFile}"&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;"options"&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;"lintFilePatterns"&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="s2"&gt;"apps/webapp/**/*.{ts,tsx,js,jsx}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"apps/webapp/package.json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;here!&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="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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your root &lt;code&gt;.eslintrc.json&lt;/code&gt; file you'll want to turn the rule on for your json files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"root"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignorePatterns"&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;"**/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&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;"@nx"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&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;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;"*.json"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"parser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jsonc-eslint-parser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rules"&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;"@nx/dependency-checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&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="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="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;Now you should be able to run the linter via &lt;code&gt;nx run-many --target=lint&lt;/code&gt; to catch any errors across any of your packages, and you can &lt;code&gt;nx run-many --target=lint --fix&lt;/code&gt; to automatically fix all errors as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Goes Brrrrrr
&lt;/h2&gt;

&lt;p&gt;Nx has continued to get faster!&lt;/p&gt;

&lt;p&gt;Our biggest speed increase has come from moving our task hashing into the Nx Daemon. A daemon process refers to a process that runs continuously in the background.&lt;/p&gt;

&lt;p&gt;Because the daemon persists between command runs, we are able to have more of the work that goes into hashing a task cached, which we added in Nx 16.3!&lt;/p&gt;

&lt;p&gt;In Nx 16.4 we also moved to a rust-based watcher to further improve performance. We had already began the migration of some of the more performance-intensive computation of Nx to Rust in prior version, but we're excited to bring more of the core of Nx into Rust and see more of these performance gains!&lt;/p&gt;

&lt;p&gt;Since our last benchmarking of Nx in October of last year where we clocked Nx at 276.2ms per command, these speed boosts have now gotten us down to 149.3ms, nearly doubling our speed!!&lt;/p&gt;

&lt;p&gt;You can see our results and the details of the benchmark - and even run the benchmarks for yourself in &lt;a href="https://github.com/vsavkin/large-monorepo"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Console Revamped
&lt;/h2&gt;

&lt;p&gt;Nx Console got a new coat of paint in both the VsCode and JetBrains (IntelliJ/Webstorm) IDEs!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6Db6-ba3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/BJgoKfxY2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6Db6-ba3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/BJgoKfxY2.png" alt="VsCode" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0H-s9aE5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/H1lstGxY3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0H-s9aE5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/H1lstGxY3.png" alt="IntelliJ" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As part of the redesign, we also moved our webviews to the Lit framework - checkout all the latest updates in this video: &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/p455D4W7330"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Changelog Launched
&lt;/h2&gt;

&lt;p&gt;Last but CERTAINLY not least, we've launched &lt;a href="https://nx.dev/reference/changelog"&gt;a new changelog&lt;/a&gt; to our docs site!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--keRIXH9D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HJM5dZlF3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--keRIXH9D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://hackmd.io/_uploads/HJM5dZlF3.png" alt="The new Nx Changelog" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This changelog includes links to all the release notes for all major and minor versions, as well as links to patch versions. We made sure to also include any deprecations or breaking changes brought about by each version as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;That's all for now folks! We're just starting up a new iteration of development on Nx, so be sure to subscribe to our YouTube channel to get updates when new features land! Until next time, KEEP WORKING HARD!&lt;/p&gt;

&lt;h3&gt;
  
  
  Learn more
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;a href="https://nx.dev"&gt;Nx Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👩‍💻 &lt;a href="https://github.com/nrwl/nx"&gt;Nx GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://go.nrwl.io/join-slack"&gt;Nx Community Slack&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📹 &lt;a href="https://www.youtube.com/@nxdevtools"&gt;Nx Youtube Channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🥚 &lt;a href="https://egghead.io/courses/scale-react-development-with-nx-4038"&gt;Free Egghead course&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://nx.app"&gt;Speed up your CI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Nx Release Notes:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-16-is-here-69584ec87053"&gt;Nx 16.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-8-rust-hasher-nx-console-for-intellij-deno-node-and-storybook-aa2b8585772e"&gt;Nx 15.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-7-node-support-angular-lts-lockfile-pruning-46f067090711"&gt;Nx 15.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-4-vite-4-support-a-new-nx-watch-command-and-more-77cbf6c9a711"&gt;Nx 15.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nrwl.io/nx-15-3-standalone-projects-vite-task-graph-and-more-3ed23f7827ed"&gt;Nx 15.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>nx</category>
      <category>nextjs</category>
      <category>verdaccio</category>
    </item>
    <item>
      <title>What's New With Lerna 6.5?</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Wed, 22 Feb 2023 17:01:23 +0000</pubDate>
      <link>https://dev.to/nx/whats-new-with-lerna-65-1ihb</link>
      <guid>https://dev.to/nx/whats-new-with-lerna-65-1ihb</guid>
      <description>&lt;p&gt;In case you missed it, Lerna version 6.5 recently launched. We'll catch you up on the latest Lerna news and newest features.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ASCSTKX21jE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lerna: Brought to You by Nx&lt;/li&gt;
&lt;li&gt;Still On Lerna 4?&lt;/li&gt;
&lt;li&gt;Idempotency Added to the &lt;code&gt;lerna publish from-git&lt;/code&gt; Command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lerna run&lt;/code&gt; Can Run Multiple Scripts In a Single Command&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;--include-private&lt;/code&gt; Option Added To &lt;code&gt;lerna publish&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Massive Refactor&lt;/li&gt;
&lt;li&gt;Getting Started With Lerna From The Lerna Team&lt;/li&gt;
&lt;li&gt;The Future of Lerna&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lerna: Brought to You by Nx &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In case you missed it, Lerna, the OG JavaScript monorepo tool, went largely unmaintained for a while, starting around 2020. Then, it officially declared itself to be unmaintained in April of 2022, only for Nx to step in to take over maintenance of the project in May of 2022!&lt;/p&gt;

&lt;p&gt;You can find a more detailed account of Lerna's "Maintainance Odyssey" in &lt;a href="https://blog.nrwl.io/lerna-is-dead-long-live-lerna-61259f97dbd9" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since Nx took over in Lerna 4, we've added a brand new site to refresh the Lerna Docs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://lerna.js.org/" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FHkbKxDjTj.png" alt="https://lerna.js.org"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The top of our priorities for Lerna 5 was to resolve all vulnerabilities and outdated dependencies facing Lerna. We went on to make Lerna faster by allowing users to &lt;a href="https://github.com/lerna/lerna/tree/main/packages/lerna/src/commands/add-caching#readme" rel="noopener noreferrer"&gt;opt into Nx's task caching inside of Lerna with the new &lt;code&gt;lerna add-caching&lt;/code&gt; command&lt;/a&gt;, and &lt;a href="https://lerna.js.org/docs/features/share-your-cache" rel="noopener noreferrer"&gt;add support for distributed caching to share task results amongst your organization in Lerna with Nx Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We were proud to go on to launch &lt;a href="https://blog.nrwl.io/lerna-reborn-whats-new-in-v6-10aec6e9091c" rel="noopener noreferrer"&gt;Lerna 6&lt;/a&gt; last October, where we began focusing on further improving Lerna's feature set - focusing specifically on its unique strengths: versioning and publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still on Lerna 4? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lerna.js.org/upgrade" rel="noopener noreferrer"&gt;Here's how to upgrade to the latest and greatest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We've also started an initiative to assist Open Source projects in getting the most out of Lerna. Projects that use Lerna can now request free consulting to learn how to take advantage of Lerna's newest features.&lt;/p&gt;

&lt;p&gt;We've just started this initiative and have already been able to help &lt;a href="https://github.com/getsentry/sentry-javascript" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; get optimized with task caching and task pipeline optimizations for their workspace!&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%2Fhackmd.io%2F_uploads%2FB1jBEkfRo.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%2Fhackmd.io%2F_uploads%2FB1jBEkfRo.png" alt="Nx Cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This initiative complements &lt;a href="https://nx.app/pricing/#open-source" rel="noopener noreferrer"&gt;our free tier of unlimited Nx Cloud&lt;/a&gt; for any Open Source project.&lt;/p&gt;

&lt;p&gt;If you're interested in optimizing your Open Source project to take advantage of the latest Lerna features, &lt;a href="https://twitter.com/lernajs" rel="noopener noreferrer"&gt;reach out to us on Twitter!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's jump into the newest Lerna 6.5  features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency Added to the &lt;code&gt;lerna publish from-git&lt;/code&gt; Command &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/kh4TaiKbC8c"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;"Idempotent" is a word used to describe an operation you can perform any number of times, and the resulting state is the same as if you had only run the operation once.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/lerna/lerna/tree/main/libs/commands/publish#readme" rel="noopener noreferrer"&gt;&lt;code&gt;lerna publish&lt;/code&gt; command&lt;/a&gt; is a beneficial tool for quickly publishing multiple packages from your workspace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running &lt;code&gt;lerna publish&lt;/code&gt; by itself will version and publish all packages in the workspace since your latest release&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;lerna publish from-git&lt;/code&gt; will publish all projects tagged in the latest commit&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;lerna publish from-package&lt;/code&gt; will publish all projects whose version does not yet exist in the target registry based on the version listed in their &lt;code&gt;package.json&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we can see, &lt;code&gt;lerna publish from-package&lt;/code&gt; is already idempotent (since it only publishes packages whose version doesn't exist, any run past the first will not adjust the state of the registry).&lt;/p&gt;

&lt;p&gt;With 6.5, we've added the same idempotency to &lt;code&gt;lerna publish from-git&lt;/code&gt;. This update is handy for recovering from a situation where some of your packages failed to publish initially (maybe due to a networking issue).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lerna/lerna/pull/3513" rel="noopener noreferrer"&gt;For more information, check out the PR&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;lerna run&lt;/code&gt; Can Run Multiple Scripts In a Single Command &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;For 6.5, we've added the ability to run multiple scripts in a single &lt;code&gt;lerna run&lt;/code&gt; command! Checkout this quick video demonstrating this below:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Ey73CEGcVKw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Learn more &lt;a href="https://github.com/lerna/lerna/pull/3527" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  New &lt;code&gt;--include-private&lt;/code&gt; Option Added To &lt;code&gt;lerna publish&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.npmjs.com/cli/v9/configuring-npm/package-json#private" rel="noopener noreferrer"&gt;Npm supports a &lt;code&gt;"private": true&lt;/code&gt; configuration&lt;/a&gt; as a way of preventing the publication of a library that is private.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; lerna publish from-git &lt;span class="nt"&gt;--include-private&lt;/span&gt; my-private-package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;lerna publish&lt;/code&gt; with this new &lt;a href="https://github.com/lerna/lerna/tree/main/libs/commands/publish#--include-private" rel="noopener noreferrer"&gt;&lt;code&gt;--include-private&lt;/code&gt;&lt;/a&gt; option (as above) will strip this &lt;code&gt;"private": true&lt;/code&gt; configuration from the &lt;code&gt;package.json&lt;/code&gt; of the packages listed in the command.&lt;/p&gt;

&lt;p&gt;This new option is beneficial for the use case where you'd like to run e2e for a package that will eventually be public but is currently private to prevent getting published too soon.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/7TgjCk7Diks"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can find more information on this change [here]((&lt;a href="https://github.com/lerna/lerna/pull/3503" rel="noopener noreferrer"&gt;https://github.com/lerna/lerna/pull/3503&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Massive Refactor &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Unlike the other updates mentioned for 6.5, this update does not affect Lerna's public API, but as you can see from the numbers, this was quite an undertaking: &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%2Fhackmd.io%2F_uploads%2FS1p7-q_ao.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%2Fhackmd.io%2F_uploads%2FS1p7-q_ao.png" alt="1,875 files changed"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FHJ8Nb5_Tj.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%2Fhackmd.io%2F_uploads%2FHJ8Nb5_Tj.png" alt="+62,345 lines, -69,303 lines"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is a significant improvement to the Typescript support for Lerna's internals and a substantial simplification of the codebase. This investment will make Lerna significantly more approachable to other would-be contributors!&lt;/p&gt;

&lt;p&gt;Find more on this change &lt;a href="https://github.com/lerna/lerna/pull/3517" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started With Lerna From The Lerna Team &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We recently ran a live stream with &lt;a href="https://twitter.com/MrJamesHenry" rel="noopener noreferrer"&gt;James Henry&lt;/a&gt; and &lt;a href="https://twitter.com/AustinFahsl" rel="noopener noreferrer"&gt;Austin Fahsl&lt;/a&gt; from our Lerna team to show how to get started with Lerna, all the way through to versioning and publishing our packages to npm!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/HqPOoU35xzA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Check out the recap of this session above, and check out &lt;a href="https://github.com/ZackDeRose/for-the-lulz" rel="noopener noreferrer"&gt;the repo from our session on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Lerna &lt;a&gt;
&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Looking to the future, we are targeting Q2 of 2023 for Lerna v7 (including a &lt;code&gt;--dry-run&lt;/code&gt; option for both &lt;code&gt;lerna version&lt;/code&gt; and &lt;code&gt;lerna publish&lt;/code&gt; commands - you can catch a sneak-peak of this in James' talk from Nx Conf 2022 below!)&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/CNdDv2MsBuw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can find &lt;a href="https://github.com/lerna/lerna/discussions/3410" rel="noopener noreferrer"&gt;a roadmap for all the features we plan to add in Lerna 7&lt;/a&gt; on Github!&lt;/p&gt;

&lt;h2&gt;
  
  
  Lerna More!
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lerna.js.org/" rel="noopener noreferrer"&gt;🧠 Lerna Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lerna/lerna" rel="noopener noreferrer"&gt;👩‍💻 Lerna GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.nrwl.io/join-slack" rel="noopener noreferrer"&gt;💬 Nrwl Community Slack (join the #lerna channel)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/nrwl_io" rel="noopener noreferrer"&gt;📹 Nrwl Youtube Channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nrwl.io/contact-us" rel="noopener noreferrer"&gt;🧐 Need help with Angular, React, Monorepos, Lerna, or Nx? Talk to us!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you liked this, click the 👏 and make sure to follow &lt;a href="https://twitter.com/lernajs" rel="noopener noreferrer"&gt;Lerna&lt;/a&gt; and &lt;a href="https://twitter.com/ZackDeRose" rel="noopener noreferrer"&gt;Zack&lt;/a&gt; on Twitter for more!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepos</category>
      <category>lerna</category>
    </item>
    <item>
      <title>Create Your Own tRPC Stack!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Thu, 26 Jan 2023 15:38:55 +0000</pubDate>
      <link>https://dev.to/nx/create-your-own-trpc-stack-3lg7</link>
      <guid>https://dev.to/nx/create-your-own-trpc-stack-3lg7</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Evolution of Awesome Tec Stacks&lt;/li&gt;
&lt;li&gt;Building Our Concrete Example Stack&lt;/li&gt;
&lt;li&gt;Creating a Code Generation Script&lt;/li&gt;
&lt;li&gt;Using that Script&lt;/li&gt;
&lt;li&gt;Defining a Task Executor&lt;/li&gt;
&lt;li&gt;Using that Executor&lt;/li&gt;
&lt;li&gt;Putting it All Together&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Evolution of Awesome Tech Stacks &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  LAMP - the OG Tech Stack
&lt;/h3&gt;

&lt;p&gt;Arguably the first tech stack that was called as such was &lt;a href="https://en.wikipedia.org/wiki/LAMP_software_bundle" rel="noopener noreferrer"&gt;the LAMP stack&lt;/a&gt;, which was traced back to a 1998 issue of the German computing magazine: Computertechnik. &lt;/p&gt;

&lt;p&gt;LAMP here was an acronym that stood for:&lt;/p&gt;

&lt;p&gt;L - Linux (the operating system)&lt;br&gt;
A - Apache (the HTTP server)&lt;br&gt;
M - MySql, or sometimes MariaDB (the database)&lt;br&gt;
P - PHP / Perl / Python (the programing language)&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%2Fhackmd.io%2F_uploads%2FB1ZBHHRij.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%2Fhackmd.io%2F_uploads%2FB1ZBHHRij.png" alt="The LAMP stack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was something magical about this idea of a tech stack. This bundle of free-to-use, open-sourced, and relatively interchangeable software was a viable (maybe even preferable!) alternative to the many paid, proprietary, and "locking-in" packages that were popular at the time, and gave rise to some killer applications - like WordPress!&lt;/p&gt;

&lt;p&gt;An interesting essential part of this idea of "a stack" was the concept of interchangeable pieces - note how the "P" in particular could stand for 3 VERY different programming languages, and a close cousin to LAMP was &lt;a href="https://en.wikipedia.org/wiki/LAMP_(software_bundle)#WAMP" rel="noopener noreferrer"&gt;WAMP&lt;/a&gt; which exchanged Linux for Windows.&lt;/p&gt;
&lt;h3&gt;
  
  
  First-Gen JS stacks: MEAN/MERN
&lt;/h3&gt;

&lt;p&gt;As we move on from the early 2000s to the 2010s - the JavaScript ecosystem, in particular, started growing in popularity, spurred by the creation of &lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; in 2009, which was accompanied by a JS package manager: &lt;a href="https://www.npmjs.com/about" rel="noopener noreferrer"&gt;npm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this popularity came the rise of the first all-JS stack - the MEAN stack, which stood for:&lt;/p&gt;

&lt;p&gt;M - Mongo - a &lt;a href="https://www.mongodb.com/databases/json-database" rel="noopener noreferrer"&gt;JSON (JavaScript Object Notation) based Database&lt;/a&gt;&lt;br&gt;
E - &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express&lt;/a&gt; - a Web Framework for Node.js&lt;br&gt;
A - &lt;a href="https://angularjs.org/" rel="noopener noreferrer"&gt;AngularJS&lt;/a&gt; - a frontend JS web framework&lt;br&gt;
N - &lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;NodeJS&lt;/a&gt; - a backend Javascript runtime environment&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%2Fhackmd.io%2F_uploads%2FHyV2mrRii.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%2Fhackmd.io%2F_uploads%2FHyV2mrRii.png" alt="The MEAN Stack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A later evolution of this stack was the MERN stack that exchanged &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; with AngularJS as the frontend web framework.&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%2Fhackmd.io%2F_uploads%2FBkcJNHAsi.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%2Fhackmd.io%2F_uploads%2FBkcJNHAsi.png" alt="The MERN Stack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this first generation of JavaScript stacks made for a recognizable and searchable term for describing a typical setup, there was little in the way of standardizing this stack.&lt;/p&gt;
&lt;h3&gt;
  
  
  Current-Gen JS stacks: T3, tRPC, Tanstack
&lt;/h3&gt;

&lt;p&gt;Stepping into the current generation of JavaScript in the 2020s - and while we're still early in this generation of JS stacks, a clear trend can be seen in the form of a focus on tooling and the developer experience.&lt;/p&gt;

&lt;p&gt;A stand-outs from this generation of the JS stack is &lt;a href="https://create.t3.gg" rel="noopener noreferrer"&gt;the t3 stack&lt;/a&gt;, which is an opinionated and evolving collection of the following technologies:&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%2Fhackmd.io%2F_uploads%2Fr1qFRBAoj.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%2Fhackmd.io%2F_uploads%2Fr1qFRBAoj.png" alt="Taken from https://create.t3.gg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A marked difference we can see from the MEAN/MERN stacks of the previous generation is that while MEAN/MERN operated mainly as a convenient search term to give needed context to looking up problems, the t3 stack is much more formalized, including a website, a community of support on discord and Twitter, and importantly: &lt;a href="https://www.npmjs.com/package/create-t3-app" rel="noopener noreferrer"&gt;a CLI tool&lt;/a&gt; to create a working starter application.&lt;/p&gt;

&lt;p&gt;This CLI is exciting as it is a way of standardizing and versioning the stack over time. If the community identifies a specific tool or enhancement that it can canonically bring into the stack, the "generative" nature of this tool gives developers a working starting point. Still, they can work from there to bring in their tools to further customize their stack.&lt;/p&gt;

&lt;p&gt;The impact on the developer, however, is the ultimate goal here, as the t3 tenants of ruthless practicality:&lt;/p&gt;

&lt;p&gt;Developers should be focused on solving their problems.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://www.npmjs.com/package/create-t3-app" rel="noopener noreferrer"&gt;&lt;code&gt;create-t3-app&lt;/code&gt;&lt;/a&gt; clearly demonstrates this: after answering a short series of questions, the developer has an operational full-stack application that they can see in action with one simple command to start up the project locally.&lt;/p&gt;

&lt;p&gt;Other popular packages in this generation include the &lt;a href="https://tanstack.com/" rel="noopener noreferrer"&gt;tanstack series of packages&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tanstack.com/" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FBk0MHUAij.png" alt="tanstack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FSksOrUAoj.png" alt="tRPC"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While not fully-fledged stacks the way t3 is, these projects are likely successful mainly due to the attention given to tooling - particularly type safety. This positions them as obvious building blocks for other stacks to incorporate.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Next Step: Nx
&lt;/h3&gt;

&lt;p&gt;We think that &lt;a href="https://nx.dev" rel="noopener noreferrer"&gt;Nx&lt;/a&gt; is uniquely positioned to be an essential tool in this current generation of JS stacks.&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%2Fhackmd.io%2F_uploads%2Fryte9UCjj.gif" 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%2Fhackmd.io%2F_uploads%2Fryte9UCjj.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nx is built on a series of core tooling features that benefit most repositories (especially &lt;a href="https://monorepo.tools/" rel="noopener noreferrer"&gt;monorepos&lt;/a&gt;), including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code generation mechanisms that allow you to quickly scaffold an entire project structure or add features such as Tailwind to an existing project&lt;/li&gt;
&lt;li&gt;a way of visualizing how sections of your code are connected via &lt;a href="https://nx.dev/core-features/explore-graph" rel="noopener noreferrer"&gt;the project graph&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;mechanisms to ensure speed while just running commands via &lt;a href="https://nx.dev/concepts/affected" rel="noopener noreferrer"&gt;the &lt;code&gt;nx affected&lt;/code&gt; command&lt;/a&gt; and &lt;a href="https://nx.dev/core-features/cache-task-results" rel="noopener noreferrer"&gt;task caching&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;a mechanism for defining how commands in your workspace depend on each other via &lt;a href="https://nx.dev/concepts/task-pipeline-configuration" rel="noopener noreferrer"&gt;task pipeline configuration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;a way to easily extend tooling via a plugin mechanism&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While all the above features help make things faster for any project, Nx's pluggability is particularly interesting for our discussion about stacks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nx.dev/packages" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FHkQdnLRii.png" alt="Official Nx Packages List"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Nx, we maintain many packages that are &lt;strong&gt;designed to be used as building blocks for developers to create their own stacks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Framework-based packages like our React, Angular, Nest, and Node packages offer developers a way of assembling their own stack based on their preferences. For instance, adding a react application to your repository with react-router-dom installed and pre-configured to match their getting started guide is as simple as running the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx g @nrwl/react:app my-app --routing


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then to complement this frontend application with a backend written with NestJS, you run the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx g @nrwl/nest:app api --frontendProject=my-app


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This will not only create a backend Nest application for you but also &lt;strong&gt;configure your React application to create a proxy for your API&lt;/strong&gt; requests to this Nest application. This otherwise tedious task will often waste developer time.&lt;/p&gt;

&lt;p&gt;Other tooling-based packages like our Vite, Webpack, and Rollup packages offer support for build, test, and Linting level tools allowing easy migration to the tool that best suits your preferences, with the added benefit of allowing for painless transitions from using webpack to vite or vice-versa.&lt;/p&gt;

&lt;p&gt;In addition to the official Nx packages comes a registry of community-supported plugins that support other frameworks, tools, and languages that we don't have official support for:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nx.dev/community#plugin-directory" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhackmd.io%2F_uploads%2FHkJoGPRso.gif" alt="Community Plugin Registry"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These community-supplied plugins offer more building blocks to create your desired stack.&lt;/p&gt;

&lt;p&gt;And our &lt;a href="https://nx.dev/packages/nx-plugin" rel="noopener noreferrer"&gt;Nx plugin package&lt;/a&gt; provides an API for creating your own building blocks and composing other building blocks into a pre-defined stack - just like the t3 stack!&lt;/p&gt;
&lt;h3&gt;
  
  
  Building Our Concrete Example Stack
&lt;/h3&gt;

&lt;p&gt;With this context in mind, let's dive into creating our concrete stack, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React as our frontend Application&lt;/li&gt;
&lt;li&gt;Tailwind for styling on our frontend app&lt;/li&gt;
&lt;li&gt;Express for our backend application&lt;/li&gt;
&lt;li&gt;Vite and Vitest for frontend bundling and testing&lt;/li&gt;
&lt;li&gt;Esbuild for backend building&lt;/li&gt;
&lt;li&gt;tRPC for building our API and full-stack type safety across both apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can build upon Nx's official plugins for the first five items and write our generators for the tRPC pieces based on their "Getting Started" documentation.&lt;/p&gt;

&lt;p&gt;The end goal is to have a codified and versioned stack that will allow us to create a full-stack application in one command and then be able to serve the entire working stack in a single command.&lt;/p&gt;

&lt;p&gt;Here's a teaser of how this will all look when we're finished:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2YwD6HRFI1I"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nrwl/nx-recipes/tree/main/plugin" rel="noopener noreferrer"&gt;And you can see and clone the workspace we'll create here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating our Plugin &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before starting this section, create an initial workspace using the command &lt;code&gt;create-nx-workspace@latest nx-trpc-example --preset=empty&lt;/code&gt;, and then install our dependencies by running the commands:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @nrwl/react @nrwl/vite @nrwl/node @nrwl/nx-plugin @nrwl/esbuild
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; yarn add @trpc/client @trpc/server
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npx nx g @nrwl/react:init
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npx nx g @nrwl/vite:init
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npx nx g @nrwl/node:init
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npx nx g @nrwl/esbuild:init


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;We'll need the above to install manually, but in the sequel to this guide, we'll create our own &lt;code&gt;init&lt;/code&gt; generator script so that any consumers of our package won't need to run this step manually!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, to create our plugin, we'll run the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx g @nrwl/nx-plugin:plugin plugin --minimal


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This will use &lt;a href="https://nx.dev/packages/nx-plugin/generators/plugin" rel="noopener noreferrer"&gt;the &lt;code&gt;plugin&lt;/code&gt; generator of the &lt;code&gt;@nrwl/plugin&lt;/code&gt; package&lt;/a&gt; to create a plugin for our workspace named: &lt;code&gt;plugin&lt;/code&gt;. We're also using the &lt;code&gt;--minimal&lt;/code&gt; flag here, so the plugin is empty initially.&lt;/p&gt;

&lt;p&gt;Once this command is run, we'll see the new project in: &lt;code&gt;libs/plugin&lt;/code&gt;&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%2Fhackmd.io%2F_uploads%2FB1peFv0ii.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%2Fhackmd.io%2F_uploads%2FB1peFv0ii.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scaffolding of this project is separated into &lt;code&gt;executors&lt;/code&gt; and &lt;code&gt;generators&lt;/code&gt; - where &lt;code&gt;executors&lt;/code&gt; are an Nx mechanism to define a task (like building an app or starting a web server for local development)  simplified to a simple Typescript function, and &lt;code&gt;generators&lt;/code&gt; are an Nx mechanism for code generation simplified to a simple Typescript function. We'll see these more in the following sections, starting with &lt;code&gt;generators&lt;/code&gt;:&lt;/p&gt;
&lt;h2&gt;
  
  
  Create a Full-Stack Application Generator &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx's Generator API simplifies any code-gen script to a simple function. The &lt;code&gt;@nrwl/devkit&lt;/code&gt; package goes on to provide utility functions to make the more burdensome things about code-generation scripts more manageable and tolerable!&lt;/p&gt;

&lt;p&gt;When we consider at a high level what our code generation should look like, we can separate it into these four steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate a front-end application&lt;/li&gt;
&lt;li&gt;generate a corresponding backend application&lt;/li&gt;
&lt;li&gt;generate a tRPC server library that is already imported into our backend application&lt;/li&gt;
&lt;li&gt;generate a tRPC client library that is already imported by our frontend application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because first-party Nx generators from the official Nx packages are functions as well, we can create a corresponding function for all four of the points above and then call those Nx generators in sequence to implement our generator.&lt;/p&gt;

&lt;p&gt;Now that we have our roadmap for the generator, we also need to consider how we'll want to parameterize our generator. The Nx CLI uses a schema-based approach to defining these options so that when another developer uses our code generation script, they can pass any of these options to the CLI, which will be passed as parameters to our generator function we'll write next.&lt;/p&gt;

&lt;p&gt;Given this, let's limit our options for now to the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the name of the full-stack application. This will be required, and we'll use this name to inform how we'll name the four projects to generate for this app: (&lt;code&gt;${name}-web&lt;/code&gt;, &lt;code&gt;${name}-server&lt;/code&gt;, &lt;code&gt;${name}-trpc-server&lt;/code&gt; and &lt;code&gt;${name}-trpc-client&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;the port for the backend and the frontend applications to run on. These won't be required - and we'll default them to 3000 and 3333, respectively.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these in mind, we'll create our application generator using the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx g @nrwl/nx-plugin:generator app --project=plugin


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Which will use &lt;a href="https://dev.to/packages/nx-plugin/generators/generator"&gt;the &lt;code&gt;generator&lt;/code&gt; generator of the &lt;code&gt;@nrwl/nx-plugin&lt;/code&gt; package&lt;/a&gt; to create our new generator named "app" located at &lt;code&gt;libs/plugin/src/generators/app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the command is run, you should see new files located in &lt;a href="https://github.com/nrwl/nx-recipes/tree/main/trpc-example-stack/libs/plugin/src/generators/app" rel="noopener noreferrer"&gt;&lt;code&gt;libs/plugin/src/generators/app&lt;/code&gt;&lt;/a&gt;, and metadata for this new &lt;code&gt;app&lt;/code&gt; generator in &lt;a href="https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/generators.json" rel="noopener noreferrer"&gt;&lt;code&gt;libs/plugin/generators.json&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The file: &lt;code&gt;libs/plugin/generators.json&lt;/code&gt; will define all the generators contained in our plugin to Nx - but we won't need to touch this file as the generator already took care of any changes required.&lt;/p&gt;

&lt;p&gt;As for the other files created, we'll start with &lt;a href="https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/src/generators/app/schema.d.ts" rel="noopener noreferrer"&gt;&lt;code&gt;libs/plugin/src/generators/app/schema.d.ts&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;frontendPort&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;backendPort&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&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;This interface matches the parameterization of the generator we want to support mentioned above. We'll also want to adjust the &lt;a href="https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/src/generators/app/schema.json" rel="noopener noreferrer"&gt;&lt;code&gt;libs/plugin/src/generators/app/schema.json&lt;/code&gt;&lt;/a&gt; file to match this as well:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This schema file will inform all Nx tools (including the CLI validation, the CLI &lt;code&gt;--help&lt;/code&gt; option, and the Nx Console tool) what options are available for this generator.&lt;/p&gt;

&lt;p&gt;Notice that the &lt;code&gt;name&lt;/code&gt; property is the only required parameter, and lines 11-14 above tell us that it is the first non-named option in our command's argument vector (or &lt;code&gt;argv&lt;/code&gt;).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx nx generate @nx-trpc-example/plugin:app &lt;span class="nb"&gt;test&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;So, for example, the above command would create our new application with the name: "test", and &lt;code&gt;frontendPort&lt;/code&gt; and &lt;code&gt;backendPort&lt;/code&gt; would use their default values. We can add specific values for these options by providing it to our command like so:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx nx generate @acme-dev/plugin:app &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--frontendPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3001


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Next, we can remove the &lt;code&gt;libs/plugin/src/generators/app/files&lt;/code&gt; directory, as we won't use that mechanism for this generator.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;@nrwl/nx-plugin&lt;/code&gt; creates this &lt;code&gt;files&lt;/code&gt; directory to support templating with Nx generators via the &lt;code&gt;writeFiles()&lt;/code&gt; function created in the &lt;code&gt;generator.ts&lt;/code&gt; file.&lt;br&gt;
The generators we're creating in this article are relatively simple, so we'll use string literals when changing a file's contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation of our generator will be written in the &lt;code&gt;libs/plugin/src/generators/app/generator.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The mental model here is we will implement these steps in the following function:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/devkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... implementation to go here!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Our &lt;code&gt;tree&lt;/code&gt; parameter represents a virtual file system of the current state of our repo. We'll use this &lt;code&gt;Tree&lt;/code&gt; API and utility functions from the &lt;code&gt;@nrwl/devkit&lt;/code&gt; package to mutate that &lt;code&gt;tree&lt;/code&gt; object until it matches the desired state.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/src/generators/app/generator.ts" rel="noopener noreferrer"&gt;find the entire contents of that file here&lt;/a&gt;, and you can see our original high-level four-step approach to the generator:&lt;/p&gt;
&lt;div class="highlight js-code-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;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createReactApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createNodeApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;serverName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;webAppName&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createTrpcServerLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createTrpcClientLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trpcClientName&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's most of our high-level view of what a generator is and how to create it - you can skip ahead to using the generator, but otherwise - let's dive in deeper by walking through the implementation of each of these functions next:&lt;/p&gt;
&lt;h3&gt;
  
  
  0. Adding Default Options and Project Names
&lt;/h3&gt;

&lt;p&gt;As a preliminary step, we'll define the default values for the optional parameters (lines 4-7 below). Then we'll create our &lt;code&gt;optionsWithDefaults&lt;/code&gt; by spreading the &lt;code&gt;defaultPorts&lt;/code&gt; with the &lt;code&gt;options&lt;/code&gt; coming in from the command. This has the effect of overwriting the &lt;code&gt;defaultPorts&lt;/code&gt; if the user provides their own, but otherwise, the defaults are used.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;We'll also import the &lt;code&gt;names&lt;/code&gt; function from the &lt;code&gt;@nrwl/devkit&lt;/code&gt; package so that we can kebab-case the name provided by the user. For example, if the user-provided name is "helloWorld", the &lt;code&gt;fileName&lt;/code&gt; of this will be "hello-world". We'll use this to get a name of our four projects in kebab-case to use later on in the generator.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Other cases supported by the &lt;code&gt;names()&lt;/code&gt; function include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pascal case

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;console.log(names('helloWorld').className); // HelloWorld&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;camel case:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;console.log(names('helloWorld').propertyName); // helloWorld&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;screaming snake case:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;console.log(names('helloWorld').constantName); // HELLO_WORLD&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;untouched:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;console.log(names('uNtOuChEd').name); // uNtOuChEd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll use some of these later on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Create Our React App
&lt;/h3&gt;

&lt;p&gt;For this step, we'll create a function called &lt;code&gt;createReactApplication()&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createReactApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation will go here!!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Our first step in this function will be to use Nx's first-party React app generator. We can import it like so:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;applicationGenerator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;reactAppGenerator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Notice we're renaming &lt;code&gt;applicationGenerator&lt;/code&gt; to &lt;code&gt;reactAppGenerator&lt;/code&gt; because we'll be importing other &lt;code&gt;applicationGenerator&lt;/code&gt;s in future steps!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When it comes time to call the function, we'll call it like so:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that Typescript Intellisense can help us with the required and optional options in lines 7-13 above!&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%2Fhackmd.io%2F_uploads%2FrkxhWdAjj.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%2Fhackmd.io%2F_uploads%2FrkxhWdAjj.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The options we've provided will set up the application with vite and vitest, and we'll give it the appropriate port. Note that if we ever wanted to adjust our bundler to webpack in the future, making that change is as simple as changing the bundler option here!&lt;/p&gt;

&lt;p&gt;Also note that since the &lt;code&gt;reactAppGenerator&lt;/code&gt; we imported is an &lt;code&gt;async function&lt;/code&gt;, we want to make sure we &lt;code&gt;await&lt;/code&gt; it! Without awaiting, we could get a race condition that would give us interesting and undesired results.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It may be a good idea to test our generator out incrementally. For example, at this point, we can confirm that our React application is generated as expected.&lt;br&gt;
To do this, I recommend using git to commit all changes you've made so far right before running the generator:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx nx g @nx-trpc-example/plugin:app &lt;span class="nb"&gt;test&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check that things look good and then reset to discard all the results so you can continue building the generator:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; HEAD


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another tool you have for previewing changes from your generator is the &lt;code&gt;--dry-run&lt;/code&gt; option. This will list all files that would have been created, updated, or deleted by the generator without actually running them:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx nx g @nx-trpx-example/plugin:app &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Nx React plugin includes a generator for adding Tailwind to a React application, so we'll import it and call it next:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setupTailwindGenerator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createReactApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;reactAppGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Linter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EsLint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;e2eTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;devServerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontendPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setupTailwindGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// rest of implementation to come here!!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Our next step is to add the boilerplate to import the tRPC client (that we'll generate in step 4!) to our new &lt;code&gt;app.tsx&lt;/code&gt; file of our React application:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getWorkspaceLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/devkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAppTsxBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;npmScope&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWorkspaceLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&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;appTsxBoilerPlate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`import { create&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;TrpcClient } from '@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;npmScope&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="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-client';
import { useEffect, useState } from 'react';

export function App() {
  const [welcomeMessage, setWelcomeMessage] = useState('');
  useEffect(() =&amp;gt; {
    create&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;TrpcClient()
      .welcomeMessage.query()
      .then(({ welcomeMessage }) =&amp;gt; setWelcomeMessage(welcomeMessage));
  }, []);
  return (
    &amp;lt;h1 className="text-2xl"&amp;gt;{welcomeMessage}&amp;lt;/h1&amp;gt;
  );
}

export default App;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`apps/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-web/src/app/app.tsx`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appTsxBoilerPlate&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;Notice that we're anticipating our import statement to match the trpc-client library that we'll create in Step 4 and using the client to query for a &lt;code&gt;welcomeMessage&lt;/code&gt; that we'll define in our trpc-server library in Step 3.&lt;/p&gt;

&lt;p&gt;Also, note that we use the &lt;code&gt;write&lt;/code&gt; method of the &lt;code&gt;Tree&lt;/code&gt; api here to write a file to our virtual file system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Other methods on the &lt;code&gt;Tree&lt;/code&gt; api include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;read()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exists()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rename()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;children()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;listChanges()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;changePermissions()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;@nrwl/devkit&lt;/code&gt; also includes a list of utility functions to manipulate our &lt;code&gt;tree&lt;/code&gt; more deftly. You can find the &lt;a href="https://nx.dev/packages/devkit/documents/index#functions" rel="noopener noreferrer"&gt;whole list here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're also using the &lt;a href="https://nx.dev/packages/devkit/documents/index#getimportpath" rel="noopener noreferrer"&gt;&lt;code&gt;getWorkspaceLayout&lt;/code&gt; function&lt;/a&gt; of the devkit so that we can match the correct import scope that Nx uses by default for Typescript imports. &lt;/p&gt;

&lt;p&gt;Our last step for now on the React application is to adjust the default port to match the port provided by the user:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;We're also using the &lt;a href="https://nx.dev/packages/devkit/documents/index#updatejson" rel="noopener noreferrer"&gt;&lt;code&gt;updateJson&lt;/code&gt; utility function&lt;/a&gt; from the &lt;code&gt;@nrwl/devkit&lt;/code&gt; to update our React app's &lt;code&gt;project.json&lt;/code&gt; file to change the &lt;code&gt;options.port&lt;/code&gt; of our &lt;code&gt;serve&lt;/code&gt; target to the provided port (line 15 above).&lt;/p&gt;

&lt;p&gt;With these pieces now created, we can finish our &lt;code&gt;createReactApplication&lt;/code&gt; function now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createReactApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;reactAppGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Linter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EsLint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;e2eTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;devServerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontendPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setupTailwindGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;createAppTsxBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;adjustDefaultDevPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  2. Create Our Backend Node App
&lt;/h3&gt;

&lt;p&gt;We'll take a similar approach to create our Node application, starting by importing the &lt;code&gt;@nrwl/node&lt;/code&gt; application generator:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Notice that we are adding a &lt;code&gt;frontendProject&lt;/code&gt; to the options (line 19 above). This will add a proxy configuration to our React app's development server - allowing us to side-step CORS complications in our local environment.&lt;/p&gt;

&lt;p&gt;We'll also adjust the contents of the &lt;code&gt;main.ts&lt;/code&gt; file that is already created after the Promise returned by this &lt;code&gt;nodeAppGenerator()&lt;/code&gt; resolves:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createServerBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;backendPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;npmScope&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWorkspaceLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&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;serverBoilerPlate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/**
 * This is not a production server yet!
 * This is only a minimal backend to get started.
 */

import express from 'express';
import * as trpcExpress from '@trpc/server/adapters/express';
import { trpcRouter } from '@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;npmScope&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="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-server';
import { environment } from './environments/environment';

const app = express();

app.use('/api', trpcExpress.createExpressMiddleware({ router: trpcRouter }));

const port = environment.port;
const server = app.listen(port, () =&amp;gt; {
  console.log(&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Listening at http://localhost:&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;{port}/api&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;);
});
server.on('error', console.error);

`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`apps/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-server/src/main.ts`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;serverBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`apps/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-server/src/environments/environment.ts`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`export const environment = {
  production: false,
  port: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;backendPort&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;Note that we're anticipating an import of the &lt;code&gt;trpcRouter&lt;/code&gt; that we'll generate in step 3, and we're also using the &lt;code&gt;backendPort&lt;/code&gt; from our options to write this port to the &lt;code&gt;environment.ts&lt;/code&gt; file, and thereby configure our backend port.&lt;/p&gt;

&lt;p&gt;Putting these pieces together, our resulting function looks like so:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createNodeApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;serverName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;nodeAppGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;serverName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Linter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EsLint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pascalCaseFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;skipFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;skipPackageJson&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;frontendProject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;createServerBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backendPort&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  3. Create a Library for Our tRPC Server
&lt;/h3&gt;

&lt;p&gt;We'll use a similar approach with the &lt;code&gt;@nrwl/js&lt;/code&gt; library generator for this lib:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;libraryGenerator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jsLibGenerator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTrpcServerLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsLibGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ... rest of the implementation to go here!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After the library is generated, we'll add the boilerplate to export our trpc from the &lt;code&gt;index.ts&lt;/code&gt; file created in this library:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTrpcServerBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;trpcServerBoilerPlate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`import { initTRPC } from '@trpc/server';

const t = initTRPC.create();

export const trpcRouter = t.router({
  welcomeMessage: t.procedure.query((req) =&amp;gt; ({
    welcomeMessage: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;Welcome to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;,
  })),
});

export type &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;TrpcRouter = typeof trpcRouter;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-server/src/index.ts`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trpcServerBoilerPlate&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;Note that this creates the &lt;code&gt;welcomeMessage&lt;/code&gt; query that we call from &lt;code&gt;app.tsx&lt;/code&gt; back in step 1!&lt;/p&gt;

&lt;p&gt;After calling these functions, we'll make a few more adjustments to clean up this library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remove the ${libraryName}.ts file that was generated&lt;/li&gt;
&lt;li&gt;remove the ${libraryName}.spec.ts file that was generated&lt;/li&gt;
&lt;li&gt;add a sourceRoot property to our &lt;code&gt;project.json&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These operations are simple enough that we can inline them in our &lt;code&gt;createTrpcServerLibrary&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTrpcServerLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsLibGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;createTrpcServerBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/lib/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcServerName&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="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/lib/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.spec.ts`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;updateJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/project.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  4. Create a Library for Our tRPC Client
&lt;/h3&gt;

&lt;p&gt;We'll use the same approach and the same &lt;code&gt;@nrwl/js&lt;/code&gt; library generator for this lib as well: &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;libraryGenerator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jsLibGenerator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTrpcClientLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;trpcClientName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsLibGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trpcClientName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitTestRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;createTrpcClientBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcClientName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/lib/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcClientName&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="nf"&gt;updateJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcClientName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/project.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;trpcClientName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTrpcClientBoilerPlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;npmScope&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWorkspaceLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&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;trpcClientBoilerPlate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`import { &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;TrpcRouter } from '@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;npmScope&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="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-server';
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';

export const create&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;TrpcClient = () =&amp;gt;
  createTRPCProxyClient&amp;lt;&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;TrpcRouter&amp;gt;({
    links: [httpBatchLink({ url: '/api' })],
  } as any);
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`libs/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-client/src/index.ts`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;trpcClientBoilerPlate&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The client here is pretty trivial and not expected to change, so we'll use 'none' for our &lt;code&gt;unitTestRunner&lt;/code&gt; option to avoid adding unit tests for this lib.&lt;/p&gt;
&lt;h3&gt;
  
  
  Putting Our Whole Generator Together
&lt;/h3&gt;

&lt;p&gt;With all this in place, we're good to go! To recap, here's the entirety of our function now:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getWorkspaceLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateJson&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/devkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;applicationGenerator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;nodeAppGenerator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;libraryGenerator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;jsLibGenerator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;applicationGenerator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;reactAppGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setupTailwindGenerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Linter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nrwl/linter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultPorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;frontendPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backendPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppGeneratorSchema&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;optionsWithDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;defaultPorts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&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;kebobCaseName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fileName&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;webAppName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kebobCaseName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-web`&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;serverName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kebobCaseName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-server`&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;trpcServerName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kebobCaseName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-server`&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;trpcClientName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kebobCaseName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-trpc-client`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createReactApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webAppName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createNodeApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;serverName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;webAppName&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createTrpcServerLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trpcServerName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createTrpcClientLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionsWithDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trpcClientName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// omitting the function implementations, but they would go below here&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Which reads like a list of exactly what we set out to do at the start!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The generators in your Local Plugin can serve as the expected scaffolding for our stack codified (as well as automated!). &lt;/p&gt;

&lt;p&gt;This way, if you decide to alter your stack or change this scaffolding - you can adjust your generators via a Pull Request.&lt;/p&gt;

&lt;p&gt;That pull request can serve as a discussion place to verify the change, and once merged, your stack will follow the new standard!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Use your Generator &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To use the generator now, run the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx generate @nx-trpc-example/plugin:app test


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Where &lt;code&gt;@nx-trpc-example&lt;/code&gt; is the name of our workspace, &lt;code&gt;plugin&lt;/code&gt; is the name of the plugin we created, &lt;code&gt;app&lt;/code&gt; is the name of the generator we created, and &lt;code&gt;test&lt;/code&gt; is the name of the full-stack app we want to create.&lt;/p&gt;

&lt;p&gt;After creating our &lt;code&gt;test&lt;/code&gt; app via the generator, we can run the command to start our server:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx serve test-server


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;And in a separate terminal, we can run the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx serve test-web


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;To run our react application that communicates with our server. This should give us our full-stack development environment to respond to our saves as long as these &lt;code&gt;serve&lt;/code&gt; commands are running. But wouldn't it be great to run this in the same terminal via a single command?&lt;/p&gt;

&lt;p&gt;In the next section, we'll create an executor that will allow us to run both of these serves in a single command.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create an Executor &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Next, we'll add a way to run both the frontend and backend applications in a development mode in a single command. As it turns out, Nx gives us a way out of the box to do this via:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

% npx nx run-many --target=serve

 &amp;gt;  NX   Running target serve for 2 projects:

    - test-server
    - test-web

 —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

&amp;gt; nx run test-server:serve


&amp;gt; nx run test-web:serve:development

Loading proxy configuration from: /Users/zackderose/nx-recipes/trpc-example-stack/apps/test-web/proxy.conf.json
  ➜  Local:   http://127.0.0.1:3000/
Debugger listening on ws://localhost:9229/7c8bd276-1df5-43e2-81cc-7de68cde56c4
Debugger listening on ws://localhost:9229/7c8bd276-1df5-43e2-81cc-7de68cde56c4
For help, see: https://nodejs.org/en/docs/inspector
Listening at http://localhost:3333/api
[ watch ] build succeeded, watching for changes...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;With that in place, both our client and server are started with the one command, but it's difficult to tell in the terminal which line belongs to which process, so we'll address this by creating an executor so that our resulting terminal looks like this:&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%2Fhackmd.io%2F_uploads%2FBy1y-c1hj.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%2Fhackmd.io%2F_uploads%2FBy1y-c1hj.png" alt="Expected Terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(That's everything for the high-level view of understanding of executors! Feel free to skip ahead to how we can use this executor once it's created, or other stick around for an in-depth explanation of the implementation details!)&lt;/p&gt;

&lt;p&gt;Our goal in Nx's task-running API is to simplify any task to a single function: an executor. When we use Nx to run a task (like with the command: &lt;code&gt;npx nx build my-app&lt;/code&gt;) Nx will determine the executor function attached to &lt;code&gt;my-app&lt;/code&gt;'s &lt;code&gt;build&lt;/code&gt; target and call that executor function!&lt;/p&gt;

&lt;p&gt;To create an executor that will serve both the front and backend applications, we'll start by running the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx nx g @nrwl/nx-plugin:executor serve-fullstack --project=plugin


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This will run &lt;a href="https://dev.to/packages/nx-plugin/generators/executor"&gt;the &lt;code&gt;executor&lt;/code&gt; generator of the &lt;code&gt;@nrwl/nx-plugin&lt;/code&gt; package&lt;/a&gt; to create an executor called &lt;code&gt;serve-fullstack&lt;/code&gt; in our &lt;code&gt;plugin&lt;/code&gt; library, with all the scaffolding managed for us.&lt;/p&gt;

&lt;p&gt;Not unlike our generator, we can adjust the parameterization of the executor here, starting with the &lt;code&gt;libs/plugin/src/executors/serve-full-stack/schema.d.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ServeFullstackExecutorSchema&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;frontendProject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;backendProject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;This will require us to provide the name of the &lt;code&gt;frontendProject&lt;/code&gt; and &lt;code&gt;backendProject&lt;/code&gt; in the options of any targets we set up for this executor. We'll also add the corresponding &lt;code&gt;libs/plugin/src/executors/serve-full-stack/schema.json&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://json-schema.org/schema"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ServeFullstack executor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"frontendProject"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The name of the frontend project to serve."&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;"backendProject"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The name of the backend project to serve."&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&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;"frontendProject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"backendProject"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;With that setup, we'll start in the &lt;code&gt;libs/plugin/src/executors/serve-fullstack/executor.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While we could take a similar approach as our generators and import the executors from our &lt;code&gt;@nrwl/...&lt;/code&gt; packages, this is not as feasible for our use case.&lt;/p&gt;

&lt;p&gt;Long-running executors (a task that is expected to keep running until canceled, for instance: a &lt;code&gt;serve&lt;/code&gt; - as opposed to executors that complete at some point, like a &lt;code&gt;build&lt;/code&gt;) return an &lt;code&gt;AsyncInterator&lt;/code&gt; from the existing first-party Nx Executors currently, which are a bit more challenging to work with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So instead, we'll use Node's &lt;code&gt;child_process&lt;/code&gt; api to use Nx's CLI to call the &lt;code&gt;serve&lt;/code&gt;'s of our web app and server. Then we'll listen to emissions from these processes' &lt;code&gt;stdio&lt;/code&gt; and &lt;code&gt;stderr&lt;/code&gt; and log them with a good-looking prefix. In the end, it should look like this:&lt;/p&gt;
&lt;h3&gt;
  
  
  Starting our Server
&lt;/h3&gt;

&lt;p&gt;We'll start by running the server first, as we'll need our server running for our web server's proxy to be created correctly when we start running it.&lt;/p&gt;

&lt;p&gt;Here's the code to do so:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;startBackendServer()&lt;/code&gt; function will return a Promise that never resolves. This will ensure that the task will never complete (this is another way of achieving a long-running task, in addition to the &lt;code&gt;AsyncIterators&lt;/code&gt; mentioned before).&lt;/p&gt;

&lt;p&gt;Line 16 above creates the child process, and lines 19-20 will ensure that if our parent process ends (like if the user pressed &lt;code&gt;CTRL-C&lt;/code&gt; while it's running), the child process will be &lt;code&gt;kill()&lt;/code&gt;ed as well.&lt;/p&gt;

&lt;p&gt;On lines 21-25, we'll also listen for the substring: &lt;code&gt;'build succeeded, watching for changes...'&lt;/code&gt; here to start up our frontend serve next!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note the second input here: the &lt;a href="https://github.com/nrwl/nx/blob/master/packages/nx/src/config/misc-interfaces.ts#L147" rel="noopener noreferrer"&gt;&lt;code&gt;ExecutorContext&lt;/code&gt;&lt;/a&gt; on line 9 above. &lt;/p&gt;

&lt;p&gt;We won't use this in the generator in this example. Still, when Nx calls this function to run our executor, it will provide all of this potentially helpful context information in the second parameter here, which is useful for many other use cases!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Starting our React App
&lt;/h3&gt;

&lt;p&gt;We'll use the following function to start up our frontend server:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startFrontendServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontendProject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;childProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`npx nx serve &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;frontendProject&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;span class="na"&gt;maxBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LARGE_BUFFER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;childProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;childProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This should look very similar to the function we wrote to start our backendProject!&lt;/p&gt;

&lt;p&gt;With this function in place, we can adjust our &lt;code&gt;startBackendServer()&lt;/code&gt; function now to call this only the first the backend server starts correctly:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Notice we added a variable to track whether we've started the frontend server before on line 2 above, and we added a check in line 10, just before calling our &lt;code&gt;startFrontendServer()&lt;/code&gt; function on line 11.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Good-Looking Prefixes to Our logs
&lt;/h3&gt;

&lt;p&gt;At this point, our executor is functional, but if we run it right now, the logs will be empty! We'll fix this by creating a function that, when given a &lt;code&gt;ChildProcess&lt;/code&gt; and a &lt;code&gt;prefix&lt;/code&gt;, will relay anything logged by the process to the parent's &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since we want the prefixes to look nice, later, we'll use &lt;code&gt;chalk&lt;/code&gt; (that Nx already has as a dependency, so it's already installed) and add some logic to center the name of the project and make all prefixes take the same amount of space:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Lines 2-12 above create a reusable function to prefix every line of any incoming string and send it to the parent process's &lt;code&gt;stdout&lt;/code&gt; by calling &lt;code&gt;console.log()&lt;/code&gt;, and lines 13-16 set up hooks to call this function whenever the given &lt;code&gt;ChildProcess&lt;/code&gt; writes to &lt;code&gt;stdout&lt;/code&gt; or &lt;code&gt;stderr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The function to &lt;code&gt;padTargetName&lt;/code&gt; on lines 19-25 above will ensure that our prefix is centered and the same length as any other project name.&lt;/p&gt;

&lt;p&gt;All that's left is to put it all together now:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Notice lines 12-13 above determines the targeted prefix width, and we adjusted our &lt;code&gt;startBackendServer()&lt;/code&gt; and our &lt;code&gt;startFrontendServer()&lt;/code&gt; functions to accept this as a parameter as well, and lines 28-31 and 54-57 add our &lt;code&gt;prefixTerminalOutput()&lt;/code&gt; so both the &lt;code&gt;ChildProcess&lt;/code&gt;es are prefixed correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use your Executor &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To use our executor, we'll start by adding a new target to our &lt;code&gt;apps/test-web/project.json file&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Lines 8-14 above add a new &lt;code&gt;serve-fullstack&lt;/code&gt; target to our &lt;code&gt;test-web&lt;/code&gt; app that we already created with our generator. Line 9 tells it to use the executor we just wrote, and lines 10-13 give that executor the required parameters we set up for it.&lt;/p&gt;

&lt;p&gt;All that's left to do now is call this target from the Nx CLI:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx nx serve-fullstack test-web


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fhackmd.io%2F_uploads%2FBy1y-c1hj.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%2Fhackmd.io%2F_uploads%2FBy1y-c1hj.png" alt="expected output"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Putting it All Together &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As a bonus step, now that we have our executor set up, we can automate this step of adding the &lt;code&gt;serve-fullstack&lt;/code&gt; target to the right &lt;code&gt;project.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Going back to &lt;code&gt;libs/plugin/src/generators/app/generator.ts&lt;/code&gt;, and specifically the &lt;code&gt;createReactApplication()&lt;/code&gt; function, we can add another function called &lt;code&gt;addFullStackServeTarget&lt;/code&gt; and call it after generating the rest of our react application:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Starting at line 26, we use &lt;a href="https://nx.dev/packages/devkit/documents/index#updatejson" rel="noopener noreferrer"&gt;&lt;code&gt;@nrwl/devkit&lt;/code&gt;'s &lt;code&gt;updateJson()&lt;/code&gt; function&lt;/a&gt; to do just this to the &lt;code&gt;project.json&lt;/code&gt; of the react app that we'll create in this generator.&lt;/p&gt;

&lt;p&gt;As we can see in the result, the whole picture of what we've created allows us to "stamp out" any number of full stack applications to add to our monorepo's workspace, given simply a name (and optionally a frontend or backend port!). We are then fully set up to execute a full-stack serve where we can easily distinguish the logs from our frontend vs. backend serve processes, as we saw in our original teaser:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2YwD6HRFI1I"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at the history and evolution of tech stacks and saw how Nx is positioned to be an integral part of creating and maintaining tech stacks in the future!&lt;/p&gt;

&lt;p&gt;While this plugin currently exists only within our current workspace that we built together, we'll follow up this blog post with another on how to publish and share your plugin with the broader community (including an &lt;code&gt;init&lt;/code&gt; generator for installing required packages, and a &lt;code&gt;preset&lt;/code&gt; generator for creating a new workspace from scratch!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;🧠 Nx Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nrwl/nx" rel="noopener noreferrer"&gt;👩‍💻 Nx GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.nrwl.io/join-slack" rel="noopener noreferrer"&gt;💬 Nrwl Community Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@nxdevtools" rel="noopener noreferrer"&gt;📹 Nrwl Youtube Channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nrwl.io/contact-us" rel="noopener noreferrer"&gt;🧐 Need help with Angular, React, Monorepos, Lerna, or Nx? Talk to us 😃&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you liked this, click the ❤️ and make sure to follow &lt;a href="https://twitter.com/zackderose" rel="noopener noreferrer"&gt;Zack&lt;/a&gt; and &lt;a href="https://twitter.com/NxDevTools" rel="noopener noreferrer"&gt;Nx&lt;/a&gt; on Twitter for more!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>react</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Nx 15.4 - Vite 4 Support, a new Nx Watch Command, and more!</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Thu, 22 Dec 2022 21:21:23 +0000</pubDate>
      <link>https://dev.to/nx/nx-154-vite-4-support-a-new-nx-watch-command-and-more-548</link>
      <guid>https://dev.to/nx/nx-154-vite-4-support-a-new-nx-watch-command-and-more-548</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/G02THNy3PcE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Nx just had a massive release 2 weeks ago with Nx 15.3 - if you missed it be sure to check out &lt;a href="https://dev.to/nx/nx-153-standalone-projects-vite-task-graph-and-more-49ic"&gt;our article&lt;/a&gt; featuring some huge improvements including Vite support, Standalone Angular and React presets, and a Task Graph visualization!&lt;/p&gt;

&lt;p&gt;But over the past couple of weeks, we’ve been able to land quite a few awesome features, so we’re going back at it again releasing Nx 15.4 today, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite 4 support&lt;/li&gt;
&lt;li&gt;A new nx watch command&lt;/li&gt;
&lt;li&gt;Webpack-less Cypress for React Standalone&lt;/li&gt;
&lt;li&gt;Server-Side Rendering support for Module Federation for both Angular and React Applications&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;Running Multiple Targets in Parallel for Multiple Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Interactive Custom Preset Prompts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Vite 4.0 Support &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx 15.4 brings in the latest Vite major version following the Vite 4 release earlier this month.&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%2Fuploads%2Farticles%2Fqsrvmtg2sp9l5qp9q6lx.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%2Fuploads%2Farticles%2Fqsrvmtg2sp9l5qp9q6lx.png" alt="Vite 4 launch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the &lt;a href="https://vitejs.dev/blog/announcing-vite4.html" rel="noopener noreferrer"&gt;Vite launch article&lt;/a&gt; mentions, we are investing in the Vite ecosystem, and now officially support a first-party Vite plugin. Nx 15.4 continues this investment with timely support for Vite 4, and we’re excited to be a part of the Vite ecosystem and a part of bringing more value to our devs through Vite support!&lt;/p&gt;

&lt;p&gt;Projects already using our &lt;a href="https://nx.dev/packages/vite" rel="noopener noreferrer"&gt;@nrwl/vite plugin&lt;/a&gt; will be automatically upgraded to Vite 4 when they upgrade to the latest Nx version with the &lt;code&gt;nx migrate&lt;/code&gt; command, and we've also simplified the configuration required to support Vite.&lt;/p&gt;

&lt;p&gt;We've also spent some effort into making the conversion of existing projects to use Vite simpler, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the ability to choose which targets you want to convert&lt;/li&gt;
&lt;li&gt;enhanced &lt;code&gt;vite.config.ts&lt;/code&gt; file configuration&lt;/li&gt;
&lt;li&gt;better DX with detailed messages during conversion&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nx.dev/packages/vite/generators/configuration" rel="noopener noreferrer"&gt;better documentation around converting using our generator &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nx.dev/packages/vite/documents/set-up-vite-manually" rel="noopener noreferrer"&gt;adding a guide to our docs for converting manually&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out more details about our Vite plugin including how to add Vite and Vitest to your existing Nx workspace by visiting our docs at &lt;a href="https://nx.dev/packages/vite" rel="noopener noreferrer"&gt;nx.dev/packages/vite&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx Watch &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nx 15.4 includes a new feature to support file-watching with Nx! Here’s how it works:&lt;/p&gt;

&lt;p&gt;Syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nx watch [projects modifier option] -- [command]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nx watch --all -- nx build \$NX_PROJECT_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the projects modifier option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can use &lt;code&gt;--all&lt;/code&gt; for all projects in the workspace&lt;/li&gt;
&lt;li&gt;or you can filter down to specific projects with the &lt;code&gt;--projects=[comma separated list of project names]&lt;/code&gt; option that can be used in conjunction with a &lt;code&gt;--includeDependentProjects&lt;/code&gt; option as well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nx watch command will support the variables &lt;code&gt;$NX_PROJECT_NAME&lt;/code&gt; and &lt;code&gt;$NX_CHANGED_FILES&lt;/code&gt;. This feature opens the door for nice developer workflows where we can provide an out-of-the-box mechanism for Nx to run relevant tasks on save, and we’re excited to see our users get their hands on this feature.&lt;/p&gt;

&lt;p&gt;Personally, I'm excited to use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx -c 'nx watch --all -- nx affected --target=test --files=\$NX_FILE_CHANGES'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To link in &lt;code&gt;nx watch&lt;/code&gt; with the &lt;code&gt;nx affected&lt;/code&gt; command to have a single watch command to run all my affected tests on save as they are affected!&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://nx.dev/recipes/other/workspace-watching" rel="noopener noreferrer"&gt;our docs&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webpack-less Cypress Support for Our React Standalone preset &lt;a&gt;&lt;/a&gt;
&lt;/h2&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%2Fuploads%2Farticles%2Finxw8003vajce864srv6.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%2Fuploads%2Farticles%2Finxw8003vajce864srv6.png" alt="React Standalone E2e Support"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We added a React Standalone preset in 15.3 to support single react application workspaces with Nx, and in 15.4, we’ve added back in Cypress for this preset.&lt;/p&gt;

&lt;p&gt;With Nx 15.4, a standalone React application will be created with an e2e directory preconfigured and optimized for running Cypress with the command &lt;code&gt;npx nx e2e e2e&lt;/code&gt; as soon as your initial workspace is generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-Side Rendering support for Module Federation for both Angular and React Applications &lt;a&gt;&lt;/a&gt;
&lt;/h2&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%2Fuploads%2Farticles%2Fhycqttsjaskkfo0u8n5k.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%2Fuploads%2Farticles%2Fhycqttsjaskkfo0u8n5k.png" alt="Module Federation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can get the benefits of both Server Side Rendering and Module Federation for your applications, which will improve page loads, Search Engine Optimization, and build times!&lt;/p&gt;

&lt;p&gt;Our existing &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;remote&lt;/code&gt; Module Federation generators have an added &lt;code&gt;--ssr&lt;/code&gt; flag that will enable Server-Side Rendering by generating the correct server files.&lt;/p&gt;

&lt;p&gt;We've also added a new executor to allow you to serve the host server locally, along with all remote servers from a single command.&lt;/p&gt;

&lt;p&gt;Learn more about this new feature &lt;a href="https://nx.dev/recipes/module-federation/module-federation-with-ssr" rel="noopener noreferrer"&gt;in our docs&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Multiple Targets in Parallel for Multiple Projects &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ROTO89i5m_4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Nx 15.4 includes updates to the &lt;code&gt;nx run-many&lt;/code&gt; command, allowing you to add multiple whitespace separated targets, as well as globs in the &lt;code&gt;projects&lt;/code&gt; option, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx run-many --target test build lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;^ this would run all &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, and &lt;code&gt;lint&lt;/code&gt; targets in your workspace, and you can now filter this down to select projects via globbing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx run-many --target test build lint --projects "domain-products-*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;^ this will now run all &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, and &lt;code&gt;lint&lt;/code&gt; targets for all projects in your workspace that start with "domain-products-".&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive Prompts for Custom Preset &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Last but not least, we’ve added support for interactive prompts for Custom Presets!&lt;/p&gt;

&lt;p&gt;In Nx, &lt;a href="https://nx.dev/packages/nx-plugin#custom-preset" rel="noopener noreferrer"&gt;presets&lt;/a&gt; are special code generation scripts that can be used to create a brand new Nx Workspace, using our &lt;code&gt;create-nx-workspace&lt;/code&gt; command.&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%2Fuploads%2Farticles%2Fanpzirw7dsh7tfdt13v6.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%2Fuploads%2Farticles%2Fanpzirw7dsh7tfdt13v6.png" alt="the published qwik-nx package"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For instance, I happen to know &lt;a href="https://twitter.com/shai_reznik" rel="noopener noreferrer"&gt;Shai Reznik&lt;/a&gt; at &lt;a href="https://builder.io" rel="noopener noreferrer"&gt;builder.io&lt;/a&gt; has been working on a qwik plugin for Nx, and since the &lt;a href="https://www.npmjs.com/package/qwik-nx" rel="noopener noreferrer"&gt;qwik-nx&lt;/a&gt; plugin that he’s published includes an &lt;a href="https://github.com/qwikifiers/qwik-nx/blob/main/packages/qwik-nx/generators.json#L33" rel="noopener noreferrer"&gt;Nx generator called “preset”&lt;/a&gt;, I can run the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx create-nx-workspace –preset=qwik-nx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the preset option matches the name of the published npm package.&lt;/p&gt;

&lt;p&gt;This custom preset feature has been around for a while, but as of 15.4 we’ve added support for these custom presets to interactively prompt the user following the initial installation step!&lt;/p&gt;

&lt;p&gt;This should open up some powerful functionality for plugin and package authors to parameterize their code generation scripts with Nx, and we’re excited to see folks like &lt;a href="https://twitter.com/shai_reznik" rel="noopener noreferrer"&gt;Shai&lt;/a&gt;, &lt;a href="https://builder.io" rel="noopener noreferrer"&gt;builder.io&lt;/a&gt;, and &lt;a href="https://qwik.builder.io/" rel="noopener noreferrer"&gt;qwik&lt;/a&gt; leverage this new feature!&lt;/p&gt;

&lt;h2&gt;
  
  
  That's it for this release.
&lt;/h2&gt;

&lt;p&gt;Follow us on our socials and on &lt;a href="https://www.youtube.com/@nxdevtools" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt; to make sure to see more news and releases as we announce them!&lt;/p&gt;

&lt;p&gt;You can find &lt;a href="https://github.com/nrwl/nx/releases/tag/15.4.0" rel="noopener noreferrer"&gt;full changelogs for the release&lt;/a&gt; on github.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Update Nx
&lt;/h2&gt;

&lt;p&gt;Updating Nx is done with the following command and will update your Nx workspace dependencies and code to the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx migrate latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating your dependencies, run any necessary migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nx migrate --run-migrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;🧠 Nx Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nrwl/nx" rel="noopener noreferrer"&gt;👩‍💻 Nx GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.nrwl.io/join-slack" rel="noopener noreferrer"&gt;💬 Nrwl Community Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@nxdevtools" rel="noopener noreferrer"&gt;📹 Nrwl Youtube Channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nrwl.io/contact-us" rel="noopener noreferrer"&gt;🧐 Need help with Angular, React, Monorepos, Lerna or Nx? Talk to us 😃&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, if you liked this, click the ❤️ and make sure to follow &lt;a href="https://twitter.com/zackderose" rel="noopener noreferrer"&gt;Zack&lt;/a&gt; and &lt;a href="https://twitter.com/NxDevTools" rel="noopener noreferrer"&gt;Nx&lt;/a&gt; on Twitter for more!&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>javascript</category>
      <category>monorepos</category>
    </item>
    <item>
      <title>Nrwl Wins | April 8, 2022</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Mon, 11 Apr 2022 14:37:29 +0000</pubDate>
      <link>https://dev.to/nx/nrwl-wins-april-8-2022-36</link>
      <guid>https://dev.to/nx/nrwl-wins-april-8-2022-36</guid>
      <description>&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__video-wrapper"&gt;
        &lt;div class="ltag__twitter-tweet__media--video-preview"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3MLfSPDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/ext_tw_video_thumb/1512464792132284423/pu/img/hwxX_70AxFq7zpPp.jpg" alt="unknown tweet media content"&gt;
          &lt;img src="/assets/play-butt.svg" class="ltag__twitter-tweet__play-butt" alt="Play butt"&gt;
        &lt;/div&gt;
        &lt;div class="ltag__twitter-tweet__video"&gt;
          
            
          
        &lt;/div&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--K2oq1Az_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1275858117893468165/SsjMbPlo_normal.jpg" alt="Nx profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Nx
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @nxdevtools
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Our first &lt;a href="https://twitter.com/hashtag/NrwlWins"&gt;#NrwlWins&lt;/a&gt;!!&lt;br&gt;&lt;br&gt;Check out 🧵⬇️ for links! 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      17:07 PM - 08 Apr 2022
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1512477176414711809" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1512477176414711809" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1512477176414711809" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


</description>
      <category>nrwl</category>
      <category>nx</category>
    </item>
    <item>
      <title>The "DeRxJSViewModel Pattern": The E=mc^2 of State Management [Part 2]</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Sun, 31 Oct 2021 07:58:17 +0000</pubDate>
      <link>https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-emc2-of-state-management-part-2-2i73</link>
      <guid>https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-emc2-of-state-management-part-2-2i73</guid>
      <description>&lt;p&gt;👋 hi all - this article is a continuation of a &lt;a href="https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-e-mc-2-of-state-management-part-1-3dka"&gt;previous article&lt;/a&gt; that explains the goals of the DeRxJS pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entirely de-couple state management code from presentational code (to the point where your state management code could be re-used across frameworks)&lt;/li&gt;
&lt;li&gt;maximize the benefits of RxJS, while minimizing the negatives&lt;/li&gt;
&lt;li&gt;next-level testing [and potential to hand over all our state-management code to AI at some point]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that article, we did most of the heavy lifting - developing our state-management code and fully testing out every edge case with "timeline testing" (allowing us to assert not only "what" state looks like, but "when" it should look that way as well).&lt;/p&gt;

&lt;p&gt;In this article, we'll bring that state management code and show how we can use this across 3 front-end "frameworks": React, Angular, and Vanilla JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  React
&lt;/h2&gt;

&lt;p&gt;One of the goals of DeRxJS [as the name suggests] is to remove actual RxJS code from our code bases. We discussed in the previous example how &lt;a href="https://www.npmjs.com/package/@derxjs/reducer"&gt;@derxjs/reducer&lt;/a&gt; can help us write our state management, leveraging RxJS, but without actually writing any RxJS code ourselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sA-WOU30--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ZackDeRose/derxjs/raw/main/derxjs-react-logo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sA-WOU30--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ZackDeRose/derxjs/raw/main/derxjs-react-logo.png" alt="@derxjs/react logo" width="813" height="921"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I'm excited to introduce &lt;a href="https://www.npmjs.com/package/@derxjs/react"&gt;@derxjs/react&lt;/a&gt; - that will allow us to leverage that same approach to our react presentation code.&lt;/p&gt;

&lt;p&gt;In all honesty, I've prioritized React as the first presentation-based package for derxjs in part because of React's popularity. But beyond that there are 2 huge reasons that I've targeted React first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;RxJS and React don't really play well together [yet!]&lt;/li&gt;
&lt;li&gt;One of my favorite things about React is how it is not domain-specific, for nearly everything but state-management! (Interestingly, I think this is almost entirely inverted from Angular, which I'd argue is domain-specific for everything BUT state-management) Marrying React and RxJS I think can close that gap, so that the state-management code you write is as domain-agnostic as the rest of your react code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In general, I'm quite long on React. My motivations are almost entirely selfish - I think if this package works the way I want it to, this will be my ideal environment for frontend development.&lt;/p&gt;

&lt;p&gt;Without further ado, here's the general api for our @derxjs/react code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TicTacToe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;DeRxJSComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;TicTacToeViewModelInputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;TicTacToeProps&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;viewModel&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeView&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;triggerMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;spaceClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userSpaceClickEvents$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resetClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userResetClickEvents$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;randomAi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;viewModel$&lt;/code&gt; is imported from our work from the previous article&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;component&lt;/code&gt; is a presentational component (we'll see that next!)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;initialValue&lt;/code&gt; is the starting value for our state (the &lt;code&gt;createInitialViewModel()&lt;/code&gt; function comes from the previous article as well)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;triggerMap&lt;/code&gt; is a [type-safe!] object that maps the name of "trigger" functions for our presentational components to Observable inputs of our &lt;code&gt;viewModel$&lt;/code&gt;. "Trigger" functions are how we'll communicate the message passing our presentation component will need to perform, and hand this off to to the @derxjs/react package to turn those into Observables (so we don't have to write any of that RxJS code ourselves).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inputs&lt;/code&gt; is our way to provide any non-reactive (or non-Observable) inputs to our &lt;code&gt;viewModel$&lt;/code&gt; function. Note we're passing our &lt;code&gt;randomAi&lt;/code&gt; function here - essentially parameterizing functionality of our &lt;code&gt;viewModel$&lt;/code&gt; this way. (Would be fun in future work to create an "unbeatable" ai as well!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This API is designed to allow you to write all presentational code as "presentational" components, delegating any smarts to your @derxjs/view-model, and using the provided trigger functions for message passing.&lt;/p&gt;

&lt;p&gt;Here's how that code ends up looking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;spaceClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;resetClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SpaceProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;spaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clickHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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;Space&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spaceCoordinates&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;SpaceProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeProps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;board&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Space&lt;/span&gt;
                &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
                &lt;span class="nx"&gt;spaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
                &lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spaceClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&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="nx"&gt;row&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="nx"&gt;column&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;))}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;triggers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Reset&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how &lt;code&gt;onClick&lt;/code&gt;s are set to those "trigger functions" we defined.&lt;/p&gt;

&lt;p&gt;Here's that code in action:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/react-ts-6wxgfx?embed=1&amp;amp;&amp;amp;" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular
&lt;/h2&gt;

&lt;p&gt;Next up: Angular! As mentioned, I'm of the opinion that Angular is generally very domain-agnostic when it comes to state management. In particular, it's very RxJS friendly.&lt;/p&gt;

&lt;p&gt;As such, I don't know if a @derxjs/angular package is really necessary. Eventually we could end up creating a package of utilities for hiding more of the RxJS code that we'd write, but I have no plans for that at the moment.&lt;/p&gt;

&lt;p&gt;Here's a look at the Typescript component code we'll write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;userResetClickObserver&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;userResetClickEvents$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userResetClickObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;userSpaceClickObserver&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;userSpaceClickEvents$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userSpaceClickObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;vm$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;randomAi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;userSpaceClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userSpaceClickEvents$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;userResetClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userResetClickEvents$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nl"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BoardIndex&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nx"&gt;handleSpaceClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userSpaceClickObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;handleResetClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userResetClickObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how we're creating our observables by creating an 'observer' property on the class at "construction time", and then in our click handler methods, we call &lt;code&gt;next()&lt;/code&gt; on those observers. (This essentially the same "message passing" as our React code, but the @derxjs/react package hid most of the actual code here)&lt;/p&gt;

&lt;p&gt;Similar to our react example, we'll see the same idea of a 'presentational' component in our template - with the one exception of passing our &lt;code&gt;viewModel$&lt;/code&gt; to the Angular async pipe at the top level of our template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tic Tac Toe&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"vm$ | async as vm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ vm.turn }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"border"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"board"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let row of rows"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let column of rows"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"handleSpaceClick({ row, column })"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            {{ vm.board[row][column] | uppercase }}
          &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"reset"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"handleResetClick()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reset&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice and simple :). Here's the stackblitz for our Angular code:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/angular-ivy-qe6zzv?embed=1&amp;amp;&amp;amp;" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Vanilla JS
&lt;/h2&gt;

&lt;p&gt;In this example, we'll use the &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents"&gt;dom-manipulation API&lt;/a&gt; to do the lifting that React and Angular were doing in their examples. Here's the simplified version of what we're doing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a "template" for our component, attaching id's to the buttons we'll need to listen to for clicks/update their text. This example is a bit fortunate as all the elements on the DOM are static (they don't need to be added or removed, so we can just leave the &lt;code&gt;Element&lt;/code&gt; objects on the DOM as-is, and change their text content. This would be significantly more difficult if this were not the case).&lt;/li&gt;
&lt;li&gt;use &lt;a href="https://rxjs.dev/api/index/function/fromEvent"&gt;&lt;code&gt;fromEvent&lt;/code&gt;&lt;/a&gt; from RxJS to get observables of the 'click' events on the buttons.&lt;/li&gt;
&lt;li&gt;Once we have our observables, pass them to that same &lt;code&gt;viewModel$&lt;/code&gt; function we used in React and Angular to create our View Model observable.&lt;/li&gt;
&lt;li&gt;Subscribe to that observable, and update the 'board' by changing the text content of the buttons to match the &lt;code&gt;board&lt;/code&gt; property on the view model object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's how that looks in the stackblitz:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/typescript-bavrh2?embed=1&amp;amp;&amp;amp;" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

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

&lt;p&gt;And that's a wrap!! Hope that this article helped spark some cool ideas. Be sure to reach out on &lt;a href="https://twitter.com/zackderose"&gt;twitter&lt;/a&gt; or check out &lt;a href="https://github.com/ZackDeRose/derxjs"&gt;the DeRxJS repo&lt;/a&gt; if you ever want to jam about state-management or good code architecture!!&lt;/p&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uSdgrUbk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idzdgnshhxtbabgvvrmw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uSdgrUbk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idzdgnshhxtbabgvvrmw.jpg" alt="Zack DeRose" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zack DeRose [or DeRxJS if you like] is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/community/experts/directory/profile/profile-zack-derose?hl=en"&gt;a GDE in Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;a recent nx conf/NgConf/RxJS Live/The Angular Show/ZDS speaker&lt;/li&gt;
&lt;li&gt;Creator of the &lt;a href="https://github.com/ZackDeRose/derxjs"&gt;@derxjs OSS packages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Senior Engineer and Engineering Manager at &lt;a href="https://nrwl.io/"&gt;Nrwl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checkout out my &lt;a href="https://zackderose.dev/videos"&gt;personal website&lt;/a&gt; for more of my dev content! And go bug &lt;a href="https://twitter.com/jeffbcross"&gt;Jeff Cross&lt;/a&gt;/&lt;a href="https://twitter.com/joerjohnson"&gt;Joe Johnson&lt;/a&gt; if you want to hire me to come help out your codebase or come help level up your team on Nx/NgRx/DeRxJS/RxJS/State Management! (I especially love building awesome stuff - and building up teams with bright developers that are eager to learn!)&lt;/p&gt;

</description>
      <category>rxjs</category>
      <category>react</category>
      <category>angular</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The "DeRxJSViewModel Pattern": The E=mc^2 of State Management [Part 1]</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Fri, 08 Oct 2021 14:15:48 +0000</pubDate>
      <link>https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-e-mc-2-of-state-management-part-1-3dka</link>
      <guid>https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-e-mc-2-of-state-management-part-1-3dka</guid>
      <description>&lt;h2&gt;
  
  
  Part 1: Creating a Blue Print, setting up TDD and Test Specs to Be CERTAIN that Our Code is Built to Spec.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What we'll learn in this article
&lt;/h3&gt;

&lt;p&gt;Hi everyone! 👋&lt;/p&gt;

&lt;p&gt;Today, I'm going to share and analyze a pattern that I've become quite fond of recently for managing state management in a way that is truly domain agnostic.&lt;/p&gt;

&lt;p&gt;We'll do this by first discussing a new pattern for implementing state management code using RxJS in this article. In a follow up article (that I've committed to releasing next friday), we'll demonstrate its "domain agnosticism" by using the same code to create a small app in vanilla JS [by which I simply mean "JS without a framework/library"], Angular, and React.&lt;/p&gt;

&lt;p&gt;There's something of a prerequisite of some more basic JS knowledge, and familiarity with RxJS would helpful here, but my goal is for this to be accessible for junior and senior engineers alike!&lt;/p&gt;

&lt;p&gt;Also before we proceed, [not-so-]coincidentally, I've recently published a new npm package to the registry that we'll be using in this series: &lt;a href="https://www.npmjs.com/package/@derxjs/view-model" rel="noopener noreferrer"&gt;🎉🎉🎉 @derxjs/view-model 🎉🎉🎉&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@derxjs/view-model" rel="noopener noreferrer"&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%2Fuploads%2Farticles%2Fqob5vohblg6w4gg4n2sq.png" alt="The @derxjs logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Installing the package is not necessary for this article, but I'm very excited for &lt;a href="https://www.npmjs.com/package/@derxjs/view-model#user-content-derxjs-roadmap" rel="noopener noreferrer"&gt;our roadmap&lt;/a&gt; as I think that this had the potential to be a huge &lt;code&gt;E = mc^2&lt;/code&gt; moment (more on that later!), so I highly recommend that you check it out. &lt;/p&gt;

&lt;p&gt;Here's how the work will break down:&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%2Fuploads%2Farticles%2Fuw9ogp9c165j1dail32y.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%2Fuploads%2Farticles%2Fuw9ogp9c165j1dail32y.png" alt="Breaking Down the Work into tasks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First part we'll need to do is create TS types, which will serve as our blueprint for the lego blocks.&lt;/p&gt;

&lt;p&gt;From there, it doesn't really matter which of the 5 second-tier tasks we take on next, but in some order, we're going to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create our ViewModel tests&lt;/li&gt;
&lt;li&gt;build out the ViewModel implementation&lt;/li&gt;
&lt;li&gt;create a "vanilla" JS app&lt;/li&gt;
&lt;li&gt;create an Angular app&lt;/li&gt;
&lt;li&gt;create a React app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In another world, we could even potentially have 5 different developers each working on these tasks in parallel if we had the resources for that!&lt;/p&gt;

&lt;p&gt;Unfortunately, my brain (and articles in general) is for the most part only single threaded, [insert huberman labs link on double focus] so instead, our path through these tasks is going to look like this:&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%2Fuploads%2Farticles%2Fynlcclvx2w8ei7ee2gsz.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%2Fuploads%2Farticles%2Fynlcclvx2w8ei7ee2gsz.png" alt="Our Path Through The tasks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, we'll create our &lt;code&gt;@derxjs/view-model&lt;/code&gt; TS types, then write out our tests, and then finally use TDD to implement our spec with absolute confidence that we built what we set out to build.&lt;/p&gt;

&lt;p&gt;Next week, we'll put this all to good use, creating 3 webapps with 3 different frameworks.&lt;/p&gt;

&lt;p&gt;So without further ado..... let's do this!!!&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%2Fc.tenor.com%2FJoc-buLiXcsAAAAd%2Fdisney-pixar.gif" 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%2Fc.tenor.com%2FJoc-buLiXcsAAAAd%2Fdisney-pixar.gif" alt="Adventure is out there!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing our tools
&lt;/h3&gt;

&lt;p&gt;[This section is designed more for junior developers - please feel free to skip these if you're already familiar with Ts and Observables and RxJS]&lt;/p&gt;

&lt;h4&gt;
  
  
  Typescript
&lt;/h4&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%2Fuploads%2Farticles%2Fx1o4adep3myqjl4y228v.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%2Fuploads%2Farticles%2Fx1o4adep3myqjl4y228v.png" alt="Typescript Logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this article, we'll be using &lt;a href="https://www.typescriptlang.org/docs/handbook/interfaces.html" rel="noopener noreferrer"&gt;Typescript interfaces&lt;/a&gt; to help us think about our problem, and to help us to architect a solution. In the context of our view model pattern, Typescript will allow us to create a "shape" of the view model data that our particular "domains" (think vanilla JS, Angular, and React) will leverage to create our html from that data (this is also known as "templating").&lt;/p&gt;

&lt;p&gt;Think of TS as a blueprint that we'll use early on in our development process to lay out our problem, that we'll continue to leverage and keep us honest as we actually build our solution out.&lt;/p&gt;

&lt;h4&gt;
  
  
  RxJS
&lt;/h4&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%2Fuploads%2Farticles%2Ffq1ikg9rkvpoecq7rsyi.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%2Fuploads%2Farticles%2Ffq1ikg9rkvpoecq7rsyi.png" alt="RxJS Logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rxjs.dev/" rel="noopener noreferrer"&gt;RxJS&lt;/a&gt; is a library we'll use to compose observable event streams (or &lt;code&gt;Observable&lt;/code&gt;s) in our code. I advise that it's best to think of &lt;code&gt;Observable&lt;/code&gt;s as "Timelines".&lt;/p&gt;

&lt;p&gt;RxJS also introduces the idea of an "operator" as a function that takes an &lt;code&gt;Observable&lt;/code&gt; and returns a new &lt;code&gt;Observable&lt;/code&gt;. In the context of our "&lt;code&gt;Observable&lt;/code&gt;s as 'Timelines'" mental model then, an operator is a function that takes a "Timeline" as input, and returns a new "Timeline" as output.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Wj7ffKH9GnU"&gt;
&lt;/iframe&gt;
&lt;br&gt;
(^ this is a talk  I gave on &lt;code&gt;Observable&lt;/code&gt;s as "Timelines" at RxJS Live 2021.)&lt;/p&gt;

&lt;p&gt;The “Pro’s” of RxJS are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extreme precision and control over code&lt;/li&gt;
&lt;li&gt;ability to write tests with a higher order of magnitude of precision&lt;/li&gt;
&lt;li&gt;Declarative, expressive, powerful code&lt;/li&gt;
&lt;li&gt;Huge potential for composability&lt;/li&gt;
&lt;li&gt;A perfect mental model and toolset for frontend web development (essentially a flurry of user-generated events with no guaranteed sequence)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “Con’s”: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes a long time to fully understand&lt;/li&gt;
&lt;li&gt;Even if YOU make the investment to learn it, hard to pass that buck off onto the next developer to use our code [makes it difficult to scale]&lt;/li&gt;
&lt;li&gt;Many complicated/confusing dimensions

&lt;ul&gt;
&lt;li&gt;"Observable joining strategies" (merge, switch, exhaust, concat)&lt;/li&gt;
&lt;li&gt;Hot vs. Cold vs. Luke-Warm (cold observable sourced with a hot observable)&lt;/li&gt;
&lt;li&gt;Behavior vs. Replay vs. plain vs. "Async" emmission strategy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;“Dangerous”

&lt;ul&gt;
&lt;li&gt;Risk of “leaky subscriptions”&lt;/li&gt;
&lt;li&gt;Risk of wrong implementation bugs&lt;/li&gt;
&lt;li&gt;Risk of poor implementation leading to even worse code (the only thing worse than complicated imperative code is a complicated mess of imperative code with a healthy chunk of “reactive” code mixed in)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;They flood the call stack, making debugging untennable (this is especially unfortunate as bugs in your RxJS code are probably the kind of bugs that we need good debugging tools for the MOST)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many ways, the pattern recommended in this article (and supported on @derxjs packages) is designed to squeeze all the awesome benefits out of RxJS, while avoiding all the nasty pitfalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our view model function will take as input an &lt;code&gt;Observable&lt;/code&gt; of our user's events&lt;/li&gt;
&lt;li&gt;It will return as output a single  &lt;code&gt;Observable&lt;/code&gt; of our &lt;code&gt;ViewModel&lt;/code&gt; interface&lt;/li&gt;
&lt;li&gt;We'll build out extensive, precise test suite BEFORE starting our implementation to ensure that we're developing EXACTLY what we set out to build.&lt;/li&gt;
&lt;li&gt;We'll use operators inside of our implementation to compose the transition of the input &lt;code&gt;Observable&lt;/code&gt;s to the output &lt;code&gt;Observable&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is the "DeRxJS View Model" Pattern?
&lt;/h3&gt;

&lt;p&gt;The basic idea for the "DeRxJS View Model" pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass Observables in (along with maybe a few functions or pieces of data)&lt;/li&gt;
&lt;li&gt;Get a single Observable out&lt;/li&gt;
&lt;li&gt;Use your framework of choice for templating (turning the output Observable from #2 into html).&lt;/li&gt;
&lt;/ul&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%2Fuploads%2Farticles%2F4js0csir90fph4tpm21j.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%2Fuploads%2Farticles%2F4js0csir90fph4tpm21j.png" alt="Diagram of the DeRxJS View Model Pattern"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  The &lt;code&gt;E = mc^2&lt;/code&gt; of State Management
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DeRxJSViewModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InputType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ViewModelType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputType&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ViewModelType&lt;/span&gt;&lt;span class="o"&gt;&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;^ The core of my &lt;code&gt;@derxjs&lt;/code&gt; approach are these 5 lines of Typescript code. It's deceptively simple, but my hypothesis is, this pattern is elegant enough to encompass any &amp;amp;&amp;amp; all possible state management requirements. It's a powerful core that has lots of potential as a pattern and mental model - as well as potential tools and utility code we can build on top of it!&lt;/p&gt;

&lt;p&gt;To use this type, simply install the package (&lt;code&gt;npm i @derxjs/view-model&lt;/code&gt;) and import the type into your code (&lt;code&gt;import { DeRxJSViewModel } from '@derxjs/view-model';&lt;/code&gt;) [don't worry, I'll show it in use in our actual code snippets as well!].&lt;/p&gt;

&lt;p&gt;If you want to opt out though - you can always just copy these 5 lines of code, and be on your merry way :).&lt;/p&gt;
&lt;h3&gt;
  
  
  Introducing the problem: Tic Tac Toe
&lt;/h3&gt;

&lt;p&gt;Our example problem for today: creating a Tic Tac Toe game.&lt;/p&gt;
&lt;h4&gt;
  
  
  Let's break this idea down to a rough spec in English:
&lt;/h4&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%2Fmedia.makeameme.org%2Fcreated%2Fstand-back-i-bf0dce.jpg" 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%2Fmedia.makeameme.org%2Fcreated%2Fstand-back-i-bf0dce.jpg" alt="Stand back. I am writing a spec"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Presentational concerns
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;The user will need to see a 3x3 grid that will operate as our game board. We'll make this a 3x3 grid of buttons, and we'll need in our view model some way of modeling/structuring this data.&lt;/li&gt;
&lt;li&gt;We'll want to allow the user to reset the game at anypoint (even while the computer is "thinking"). This should empty the board and make it their turn.&lt;/li&gt;
&lt;/ul&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%2Fuploads%2Farticles%2Fpnh99w31ohy7g5oo9qrz.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%2Fuploads%2Farticles%2Fpnh99w31ohy7g5oo9qrz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  Logical concerns
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;We'll want to introduce the idea of "turns" to the game. During the user's turn, they should be allowed to click a button, but after clicking a button, they shouldn't be allowed to move until the computer opponent makes a move. We'll also want to make this feedback prominent to the user.&lt;/li&gt;
&lt;li&gt;We'll need some AI for our computer opponent. As cool as it would be to use some GPT-3 here, instead, let's opt for a Random Number Generator (RNG) to act in the place of our AI, and we'll simulate our AI thinking by adding in a delay of 2 seconds before the computer player makes their move.&lt;/li&gt;
&lt;li&gt;If the game is over (if either our player or the computer wins, or if the board is full and neither player won) we'll want to inform the user, and they'll need hit restart to play again.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Creating a Typescript blue print
&lt;/h4&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%2Fgetsynapse.com%2Fwp-content%2Fuploads%2F2019%2F09%2Fblueprint.jpg" 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%2Fgetsynapse.com%2Fwp-content%2Fuploads%2F2019%2F09%2Fblueprint.jpg" alt="Image of a Blueprint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start by identifying the parameters of our system. The way I tend to think about this problem, the inputs come down to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the user's events selecting spaces on the board.&lt;/li&gt;
&lt;li&gt;the user's events selecting the "restart game" option.&lt;/li&gt;
&lt;li&gt;a function that will act as the ai that will define how our computer player will act.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can visualize the first 2 of these as Timelines, so we'll represent them as Observables - for the selection of spaces by the user, we'll represent that event with an object that states the column and row the user selected, and for the reset button clicks, the contents of the event actually doesn't matter much (just the fact that it happened is really all we need!).&lt;/p&gt;

&lt;p&gt;For our ai, we'll define it as some function that will take a  given &lt;code&gt;Board&lt;/code&gt; and the computer's letter (x or o) and return the &lt;code&gt;SpaceCoordinates&lt;/code&gt; for the space the computer will choose. This way we can pass in a determinist &lt;code&gt;ai&lt;/code&gt; function for reliable testing of our code [but we'll see more on this later!!].&lt;/p&gt;

&lt;p&gt;Let's transpose these to actual Typescript now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModelParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;userSpaceClickEvents$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;userResetClickEvents$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeAi&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BoardIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BoardIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BoardIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeAi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AiParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AiParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;aiLetter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up, we'll create an interface for our View Model itself. We'll represent this with a &lt;code&gt;board&lt;/code&gt; property that should contain a 3x3 array of either x's, o's, or empty spaces. The other thing we'll need is a &lt;code&gt;turn&lt;/code&gt; property that will tell us if it's our turn, the computer's turn, or if the game is over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Turn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game over - you win&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game over - you lose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;`game over - it's a tie`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to combine these into a function that takes our inputs and returns an &lt;code&gt;Observable&lt;/code&gt; of our view model. Let's set out that function's signature now!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DeRxJSViewModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@derxjs/view-model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeRxJSViewModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;TicTacToeViewModelParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;userSpaceClickEvents$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;userResetClickEvents$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ai&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation to go here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That about does it for our blueprint! Here's a rough blue print that matches our DeRxJS diagram from before:&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%2Fuploads%2Farticles%2Fwijfnob15pdcu89b5vfc.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%2Fuploads%2Farticles%2Fwijfnob15pdcu89b5vfc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we mentioned prior, one of the best things architecturally about having done this work is we have 3 independent paths we could follow from here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;implementing the function&lt;/li&gt;
&lt;li&gt;templating our component (in any of the 3 frameworks)&lt;/li&gt;
&lt;li&gt;writing tests for our function!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To reiterate: in another scenario, we could even potentially have 3 different developers each working on a different branch of these three things at once.&lt;/p&gt;

&lt;p&gt;Since our brains are mostly single-threaded though, let's start with writing tests for our function, then implementing the function, then templating!&lt;/p&gt;

&lt;h3&gt;
  
  
  TDD and setting up our Timeline Tests for our View Model
&lt;/h3&gt;

&lt;p&gt;For a sneak peak of this in real code, go check out the example .spec file in the @derxjs repo (maybe go give us a start there too if you feel like it! :))&lt;/p&gt;

&lt;p&gt;So, let's break this concept down &amp;amp;&amp;amp; use some cool visualizations in the process. Before we get into that, let's address one big hairy issue about our specific system: RNG.&lt;/p&gt;

&lt;p&gt;Our spec requires that the ai choose a valid space at random. The AI being random immediately throws off a key component of a reliable test suite: being deterministic (or "the same thing happening EVERY time").&lt;/p&gt;

&lt;p&gt;Luckily, we thought of this before designing our system :). Because the &lt;code&gt;ai()&lt;/code&gt; function is passed in as a parameter to our function, we can pass in a "deterministic AI" to our view model for our test, and then a "random AI" to our actual implementation (we could also come up with some other cool "flavors" of AI if we wanted - feel free to take this code and go nuts with it if you like! Would be cool to see someone come up with an "I always lose" AI.)&lt;/p&gt;

&lt;p&gt;To get this, let's quickly draft out this ai function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testAiFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeAi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;board&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &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;i&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &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;j&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ai was passed a full board. This should never happen&lt;/span&gt;&lt;span class="dl"&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;We should see the following behavior from this AI:&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%2Fuploads%2Farticles%2Fg1elo3vr39eqw8iqyx7n.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%2Fuploads%2Farticles%2Fg1elo3vr39eqw8iqyx7n.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Very cool. By leveraging functions (for the people who like terminology, this is technically a "higher order function" as the DeRxJSViewModel is a function that took this &lt;code&gt;ai()&lt;/code&gt; function as a parameter), we can see that we are able to pretty effortlessly make our system very highly configurable! If we wanted to - we could actually have passed in an &lt;code&gt;async&lt;/code&gt; function (or a function that returns a &lt;code&gt;Promise&lt;/code&gt;) here as well, and built in the 2 second delay right into our AI for even more configurability, but I think this is enough complexity for now.&lt;/p&gt;

&lt;p&gt;Btw - if you thought passing functions was cool - just wait until what we can do when passing in observables as input!!&lt;/p&gt;

&lt;h4&gt;
  
  
  The Test Suite
&lt;/h4&gt;

&lt;p&gt;We want to build out a test to cover our system. In general, we want to make sure we hit most of our edge cases and then maybe run the whole way through a couple times.&lt;/p&gt;

&lt;p&gt;It's usually nice to start with a nice basic base-case, so let's start with an empty board where the user never makes any actions:&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%2Fuploads%2Farticles%2F9vzlm2zgmjwsam939tzp.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%2Fuploads%2Farticles%2F9vzlm2zgmjwsam939tzp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Since we'll always use the same &lt;code&gt;ai()&lt;/code&gt; function for every test, I've skipped that in the diagrams) but as we can see, this very basic test shows that with no user events, we start out with an empty board, and that board never changes.&lt;/p&gt;

&lt;p&gt;Here's the code for this exact scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;with no user events, the board stays empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstUserState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;testScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectObservable&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;------&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userResetClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;------&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-----&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstUserState&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;userSpaceClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;userResetClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userResetClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAiFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;expectObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the ASCII art syntax!! While it's not bad here, I think it gets a bit of a bad rap for being a bit cumbersome to work with, and not without good reason! I was helping out with trying to line up these ASCII art kind of tests on the rxjs codebase, so I can vouch that it's not fun! (I think I got through 3 operators before I gave up :()&lt;/p&gt;

&lt;p&gt;(@derxjs is working on a nice gui tool to "draw" out these timelines and spit out working jest .spec.ts files - but that's an article for another time)&lt;/p&gt;

&lt;p&gt;Also notice the &lt;code&gt;testScheduler&lt;/code&gt;! We'll touch on this in the next example: &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%2Fuploads%2Farticles%2F4xwl0b2r4o5tmzfws4uf.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%2Fuploads%2Farticles%2F4xwl0b2r4o5tmzfws4uf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So in this example, we can see the player chooses the center square - note the dotted vertical line! This shows us how the timelines are aligned horizontally so that  the moment we observe the user select the middle square, we should immediately observe a new state in our UI - reflecting an 'x' in that square and showing that it is now the 'computer's' turn to move. Then 2 seconds later (2 seconds of "virtual time" thanks to our TestScheduler!!) we should see our AI move to that first open slot in the top left corner, and it should now be our player's turn again.&lt;/p&gt;

&lt;p&gt;So to recap: our timeline test is showing us that for the given inputs (the two observables/timelines) here's the expected timeline.&lt;/p&gt;

&lt;p&gt;Remember when we discussed the expressive power of the &lt;code&gt;ai()&lt;/code&gt; function as a parameter to our tic-tac-toe system? These observable inputs are actually bringing in with them AN INFINITE NUMBER OF POTENTIAL FUTURES, EVENTS, AND COMBINATIONS OF EVENTS&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%2Fc.tenor.com%2FqU7kKSP7JgsAAAAM%2Fbig-brain-lateralus.gif" 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%2Fc.tenor.com%2FqU7kKSP7JgsAAAAM%2Fbig-brain-lateralus.gif" alt="infinite worlds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and our viewModel function is [attempting] to WRANGLE EVERY SINGLE LAST ONE of them and handle them appropriately, and RxJS is masterful at allowing us to do so.&lt;/p&gt;

&lt;p&gt;Part of the reason I'm so long on these timeline tests are because of how good they are at setting a mental model - and especially with RxJS code being so difficult to debug, these marble tests become even MORE valuable! After all, you don't have to debug what ain't broke!&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%2Fc.tenor.com%2FxnDaGHW7i9QAAAAC%2Fhm-hmm.gif" 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%2Fc.tenor.com%2FxnDaGHW7i9QAAAAC%2Fhm-hmm.gif" alt="You don't have to debug what ain't broke!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the code for this test (notice things starting to get a bit more hairy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;computer player makes a move 1999ms after the player selects a square&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;userClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="na"&gt;afterUserClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&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="na"&gt;afterComputerMoves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`your turn`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;testScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectObservable&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;---a----&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userClick&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;userResetClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;------&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a--b 1999ms c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterUserClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterComputerMoves&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;userSpaceClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;userResetClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userResetClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAiFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;expectObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's move onto the next case: make sure that nothing happens when a player clicks on an already filled square (and also that nothing happens 2 seconds AFTER the user clicks that filled square.... for hopefully evident reasons!!)&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%2Fuploads%2Farticles%2Fasko0aira49br3e19kuk.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%2Fuploads%2Farticles%2Fasko0aira49br3e19kuk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user click does nothing on an already filled square&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;userClicksCenterSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="na"&gt;afterUserClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&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="na"&gt;afterComputerMoves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`your turn`&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="na"&gt;userClicksComputersSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;testScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectObservable&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;---a 3s a-b-aaaa-bbbb-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userClicksCenterSquare&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userClicksComputersSquare&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;userResetClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;------&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a--b 1999ms c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterUserClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterComputerMoves&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;userSpaceClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;userResetClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userResetClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAiFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;expectObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice our output looks the same, even though our user is spamming those selected squares!&lt;/p&gt;

&lt;p&gt;Next up: The reset button works&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%2Fuploads%2Farticles%2F1l3trc7oxnc1msmr2wo2.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%2Fuploads%2Farticles%2F1l3trc7oxnc1msmr2wo2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset button works&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;userClicksCenterSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="na"&gt;afterUserClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&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="na"&gt;afterComputerMoves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`your turn`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;testScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectObservable&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;---a------&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userClicksCenterSquare&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;userResetClicks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;---- 1999ms ---a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a--b 1999ms c--a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterUserClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;afterComputerMoves&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ticTacToeViewModel&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;userSpaceClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userBoardClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;userResetClickEvents&lt;/span&gt;&lt;span class="na"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userResetClicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAiFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;expectObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fairly straight-forward - but it will be a good marker to let us know if the most basic functionality of resetting our game is working properly&lt;/p&gt;

&lt;p&gt;Next one is around the same reset case, with a twist: the user can reset the game while the computer is "thinking" (making sure that the move the computer would have made is appropriately "cancelled"!)&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%2Fuploads%2Farticles%2F0o83nc4a46a0d2jh2yla.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%2Fuploads%2Farticles%2F0o83nc4a46a0d2jh2yla.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think that covers it for most of the potential edge cases of our system! For good measure, I'd also throw a couple fully-played out games as well just to be sure that we can make it all the way through a game without issues (I'm not gonna diagram those out here, but you can see it in action for yourself in &lt;a href="https://github.com/ZackDeRose/derxjs/blob/main/libs/examples/tic-tac-toe-view-model-implementation/src/lib/view-model.spec.ts" rel="noopener noreferrer"&gt;the @derxjs codebase :)&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Run the test suite now if you like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ZackDeRose/derxjs.git
&lt;span class="nb"&gt;cd &lt;/span&gt;derxjs
npm i
npx nx &lt;span class="nb"&gt;test &lt;/span&gt;examples-tic-tac-toe-view-model-implementation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing our "DeRxJS View Model" Pattern
&lt;/h3&gt;

&lt;p&gt;Alright, so we've got a fully fleshed out spec at this point - time to implement!&lt;/p&gt;

&lt;p&gt;(Btw - if you want to give yourself a fun challenge now - go clone my derxjs repo and erase the contents of &lt;code&gt;tic-tac-tow-view-model-implementation/src/lib/view-model.spec&lt;/code&gt;, and see if you can get the tests to pass before reading this section!)&lt;/p&gt;

&lt;p&gt;We could honestly go a couple ways with this specific problem (in particular a 'state machine' approach would be a great fit for this - and I hope to do a proper XState implementation [maybe even a @derxjs/xstate package somewhere down the line!!] at some point in the future).&lt;/p&gt;

&lt;p&gt;But because it's a pretty common pattern, we'll look at a "reducer" solution here!&lt;/p&gt;

&lt;p&gt;If you're familiar with the JavaScript Array.reduce() method, that's a great place to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;number&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;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrayItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;accumulator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;arrayItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code, we used &lt;code&gt;reduce&lt;/code&gt; to "reduce the contents of the array down to a single value: their sum." Notice that this too is a higher-order function (since it takes a function as a parameter), making it very versatile. You can do ALOT of things with reduce.&lt;/p&gt;

&lt;p&gt;We're actually interested more in the reducer function though, specifically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrayItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;accumulator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;arrayItem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea for this function is that it will loop through the list and call this function for each item; the item itself going into the &lt;code&gt;arrayItem&lt;/code&gt; param, and the return value of the prior function call going into the &lt;code&gt;accumulator&lt;/code&gt; param.&lt;/p&gt;

&lt;p&gt;In this way, we "reduce the array down to a sum"! Pretty cool.&lt;/p&gt;

&lt;p&gt;We're going to take this same concept a bit further for our tic-tac-toe example now, expanding the anaology in 2 separate dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instead of numbers, we're going to reduce some set of "observed events" (think user clicks and ai moves) down into the &lt;code&gt;TicTacToeViewModel&lt;/code&gt; interface from the prior section; here it is again if you missed it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Turn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game over - you win&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game over - you lose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;`game over - it's a tie`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&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;ol&gt;
&lt;li&gt;Instead of synchronously iterating through a defined list and returning a value at the end of it all, we're going to asynchronously handle events immediately as they are observed, emitting a corresponding state. (this one sounds intimidating, but this one is actually quite trivial thanks to RxJS!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before getting overwhelmed in all this, let's take a deep breath and break this down into it's pieces. The first step is to identify the events that could potentially change our &lt;code&gt;TicTacToeViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two of them are pretty clear (because they're input observables to our function!): user clicking a square, and user clicking reset.&lt;/p&gt;

&lt;p&gt;Let's go ahead and wrap those up now, by creating interfaces for them that wrap their value in an object and add a 'type' property to them (we'll switch on these 'type values later on!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserSpaceClickAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user space click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// export interface SpaceCoordinates {&lt;/span&gt;
&lt;span class="c1"&gt;//   row: BoardIndex;&lt;/span&gt;
&lt;span class="c1"&gt;//   column: BoardIndex;&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserResetAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user reset click&lt;/span&gt;&lt;span class="dl"&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;[You might see this elsewhere referred to as "tokenizing" (or "actionizing") the data.]&lt;/p&gt;

&lt;p&gt;There's one more event that would change our view model though: the ai's moves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AiAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&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;We'll create a &lt;a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types" rel="noopener noreferrer"&gt;union type&lt;/a&gt; of these actions via TS as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserSpaceClickAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;UserResetAction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;AiAction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of this as "grouping" the actions into a single type, where you know an &lt;code&gt;Action&lt;/code&gt; is one of the 3 possible.&lt;/p&gt;

&lt;p&gt;Now we'll write the signature for our &lt;code&gt;reducer&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... impl here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best to think of this function as "handling" an action - so if a user clicks on a button, the next state should be the same as before, but with an 'X' on the square the user selected (and also the computer's turn - or maybe the user even won with that move and we should say "You Win!!"):&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%2Fuploads%2Farticles%2Fr0zjcjm13mpseqxly743.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%2Fuploads%2Farticles%2Fr0zjcjm13mpseqxly743.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or if the user clicks "reset" we should get a new board:&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%2Fuploads%2Farticles%2Fb3h5alnhis82x0d5hqxw.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%2Fuploads%2Farticles%2Fb3h5alnhis82x0d5hqxw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or if the user clicks on an occupied square, or tries to act while it's the computer's turn, we should just stay the same.&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%2Fuploads%2Farticles%2F1p6qefzwem6hww4gptx0.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%2Fuploads%2Farticles%2F1p6qefzwem6hww4gptx0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully these sound familiar, because these are pretty much exactly the test cases we had tested prior! ONLY! Rather than thinking about the whole flow of data (the whole timeline) instead we're focused in on the micro: handling a single action.&lt;/p&gt;

&lt;p&gt;Before we actually write these functions though, let's create some utility functions for the things we'll need:&lt;br&gt;

  TicTacTo Utility Fns
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isSpaceOccupied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * assumes the given `space` is unoccupied!
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nextBoard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Board&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;newBoard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[...&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]];&lt;/span&gt;
  &lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&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;newBoard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isGameOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user wins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;computer wins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tie game&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;false&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;winningCombinations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SpaceCoordinates&lt;/span&gt;
  &lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;winningCombinations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user wins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;computer wins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SpaceContent&lt;/span&gt;&lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tie game&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;Functions make for really great "Lego blocks" and now that we have all these, let's write out function (throwing the real implementation in here because too many cases to cover them all in text, but good for you to look over it and make sure it makes sense):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user reset click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user space click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isSpaceOccupied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&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;newBoard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextBoard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isGameOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user wins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game over - you win&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tie game&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`game over - it's a tie`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should not be reached&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newBoard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextBoard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isGameOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your turn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;computer wins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newBoard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;game over - you lose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should not be reached&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome!! It wouldn't be a terrible idea now to write some test for this reducer function (or even writing them before we implemented this) but since we already have our test suite set up, this is not really necessary. (Even the fact that we're using a &lt;code&gt;reducer&lt;/code&gt; strategy right now is implementation detail that we don't necessarily want to preserve moving forward!)&lt;/p&gt;

&lt;p&gt;The majority of our work is done now! For the sake of time, I'm going to reveal the rest of the black box of our "reducer" implementation of the &lt;code&gt;ticTacToeViewModel$()&lt;/code&gt;:&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%2Fuploads%2Farticles%2Fvgjqmjoa6n2akhn0tqcr.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%2Fuploads%2Farticles%2Fvgjqmjoa6n2akhn0tqcr.png" alt="Mostly done now!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The shape "engine" is actually quite generic to the reducer pattern (although passing in the &lt;code&gt;ai()&lt;/code&gt; function is a little bit of a twist... but not that much). If you've worked with NgRx, Redux, or some similar library, it actually probably looks very familiar already!&lt;/p&gt;

&lt;p&gt;This "genericism" actually makes it quite nice to "package" this flow and... maybe &lt;a href="https://www.npmjs.com/package/@derxjs/reducer" rel="noopener noreferrer"&gt;publish it on npm&lt;/a&gt;??? (you can check out an example implementation for this same problem [that uses this exact same reducer function we just wrote!!] in &lt;a href="https://github.com/ZackDeRose/derxjs/blob/main/libs/examples/tic-tac-toe-view-model-implementation/src/lib/using-reducer-package.ts" rel="noopener noreferrer"&gt;the derxjs repo&lt;/a&gt;!)&lt;/p&gt;

&lt;p&gt;But in any case, let's follow the diagram and see how to translate this into code:&lt;/p&gt;

&lt;p&gt;First off, we'll take our incoming user actions and use the &lt;a href="https://rxjs.dev/api/operators/map" rel="noopener noreferrer"&gt;map&lt;/a&gt; operator to "tokenize" or wrap them into the right &lt;code&gt;Action&lt;/code&gt; type, and then &lt;a href="https://rxjs.dev/api/operators/merge" rel="noopener noreferrer"&gt;merge&lt;/a&gt; them down to create a single &lt;code&gt;actions$&lt;/code&gt; Observable. We'll go ahead here and create out &lt;code&gt;actionsSubject&lt;/code&gt; and start sharing our actions to that subject.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionsSubject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userClickActions$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userSpaceClickEvents$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;UserSpaceClickAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user space click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userResetActions$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userResetClickEvents$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;UserResetAction&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user reset click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actions$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userClickActions$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userResetActions$&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;actionsSubject&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;&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%2Fuploads%2Farticles%2Fsswq5echsjvb09ju5u81.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%2Fuploads%2Farticles%2Fsswq5echsjvb09ju5u81.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Green shows what we've done!)&lt;/p&gt;

&lt;p&gt;Next up, we're gonna hook that actions stream up with our reducer function to create our state observable. This is the &lt;code&gt;Observable&amp;lt;TicTacToeViewModel&amp;gt;&lt;/code&gt; that we set our to make!!! But we're not done yet...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nx"&gt;startWith&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TicTacToeViewModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createInitialViewModel&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nf"&gt;distinctUntilChanged&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;^ Note how we used &lt;a href="https://rxjs.dev/api/operators/scan" rel="noopener noreferrer"&gt;scan&lt;/a&gt; with our reducer function here. What we're doing is feeding in all the user's actions one-by-one, and each action is running through that reducer to get to a next "TicTacToeViewModel" object, which is a super clean &amp;amp;&amp;amp; easy way to get to our &lt;code&gt;Observable&lt;/code&gt; of that &lt;code&gt;TicTacToeViewModel&lt;/code&gt;!!! This is where RxJS really shines in my opinion!&lt;/p&gt;

&lt;p&gt;Note we're also &lt;a href="https://rxjs.dev/api/operators/startWith" rel="noopener noreferrer"&gt;starting with&lt;/a&gt; an initialState (think just an empty board) and we're going to use &lt;a href="https://rxjs.dev/api/operators/distinctUntilChanged" rel="noopener noreferrer"&gt;distinctUntilChanged&lt;/a&gt; to filter out any times our state observable would emit the same state over again (since we're creating this view model for a ui - it really doesn't make sense to make the UI re-render if [for example] the user tried to click on a space that was already occupied!&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%2Fuploads%2Farticles%2Ff7iudm360ag007b851y7.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%2Fuploads%2Farticles%2Ff7iudm360ag007b851y7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we ran this now as is though, after the user made their first move, it would become the computer's turn, and so far, we haven't hooked up the ai piece of our code yet, so the user would just be stuck in that state....&lt;/p&gt;

&lt;p&gt;This is where "effects" come into play! The idea of an effect (thinking generically in the context of DeRxJS/reducer) is that it is a function that takes our &lt;code&gt;actions$&lt;/code&gt; Observable, and our &lt;code&gt;state$&lt;/code&gt; Observable (or just one of them.... or NEITHER!) and returns a new Observable of actions that we're going to connect BACK into our actions Subject to &lt;a href="https://rxjs.dev/api/operators/share" rel="noopener noreferrer"&gt;share&lt;/a&gt; those actions back into the actions$ observable!&lt;/p&gt;

&lt;p&gt;That's generically though - for our specific example, we only have the one effect that we need to worry about, and that's our AI!&lt;/p&gt;

&lt;p&gt;We'll start this with the observed states, &lt;a href="https://rxjs.dev/api/operators/filter" rel="noopener noreferrer"&gt;filter&lt;/a&gt;ed down to only states where the turn has just changed to "computer's turn" - we'll then &lt;a href="https://rxjs.dev/api/operators/delay" rel="noopener noreferrer"&gt;delay&lt;/a&gt; for 2 seconds before &lt;a href="https://rxjs.dev/api/operators/map" rel="noopener noreferrer"&gt;map&lt;/a&gt;ping the &lt;code&gt;Board&lt;/code&gt; that came with the state through our &lt;code&gt;ai()&lt;/code&gt; function to get our computer's move!&lt;/p&gt;

&lt;p&gt;Last step is to wrap alll that into a (switchMap)[&lt;a href="https://rxjs.dev/api/operators/switchMap" rel="noopener noreferrer"&gt;https://rxjs.dev/api/operators/switchMap&lt;/a&gt;] from the user reset actions! We do this so that if the user ever resets the game while the computer is "thinking", we'd "discard" that subscription, so that the computer doesn't randomly barge in 2 seconds after the last "user space click" action when it should be the user's turn!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aiActions$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userResetActions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;startWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;state$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`computer's turn`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;aiLetter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;})),&lt;/span&gt;
        &lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;actionsSubject&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;aiActions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&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;state$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last thing to do, subscribe to our &lt;code&gt;aiActions$&lt;/code&gt; and return the &lt;code&gt;state$&lt;/code&gt;!&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%2Fuploads%2Farticles%2Fkquhv2nweyujsrjse5lb.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%2Fuploads%2Farticles%2Fkquhv2nweyujsrjse5lb.png" alt="Alt Text"&gt;&lt;/a&gt;&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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--GBdGae-N--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fmedia4.giphy.com%2Fmedia%2FAS6bC3bKauQnwgyrad%2F200.gif" 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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--GBdGae-N--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fmedia4.giphy.com%2Fmedia%2FAS6bC3bKauQnwgyrad%2F200.gif" alt="catching my breath"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And yes - I'm aware there's a potentially leaky subscription there on the aiActions$!&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%2Fuploads%2Farticles%2F7vuhxvx7wkqtms36svds.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%2Fuploads%2Farticles%2F7vuhxvx7wkqtms36svds.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm gonna say that's okay for now (in my mind, this "game" is all our app is, so no need to teardown...) - but be sure to checkout &lt;a href="https://github.com/ZackDeRose/derxjs/blob/main/packages/reducer/src/lib/reducer.ts#L32" rel="noopener noreferrer"&gt;the DeRxJS/reducer source code&lt;/a&gt; to see how we could handle that!&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing Thoughts
&lt;/h3&gt;

&lt;p&gt;I'm very excited for the work we're doing here (and not just because of the upcoming DeRxJS roadmap 👀):&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%2Fuploads%2Farticles%2F1mo8ycwmx7bvp3hfdpxh.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%2Fuploads%2Farticles%2F1mo8ycwmx7bvp3hfdpxh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason I'm excited is that (in case you didn't notice) NONE OF THE CODE WE WROTE IN THIS ARTICLE is domain-specific. Beyond just Angular/React/Vue/etc., we could take this same 'tic-tac-toe' state management code now and drop it in a mobile app/a native app/Artificial Reality/Augmented Reality/really ANY presentation layer and our state management code would be valid, and proven to be to spec!&lt;/p&gt;

&lt;p&gt;I'm very excited to see where this takes us next - and I can't wait to see y'all in the next article!!&lt;/p&gt;

&lt;h3&gt;
  
  
  About the author
&lt;/h3&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%2Fuploads%2Farticles%2Fidzdgnshhxtbabgvvrmw.jpg" 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%2Fuploads%2Farticles%2Fidzdgnshhxtbabgvvrmw.jpg" alt="Zack DeRose"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zack DeRose [or DeRxJS if you like] is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/community/experts/directory/profile/profile-zack-derose?hl=en" rel="noopener noreferrer"&gt;a GDE in Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;a recent nx conf/NgConf/RxJS Live/The Angular Show/ZDS speaker&lt;/li&gt;
&lt;li&gt;Creator of the &lt;a href="https://github.com/ZackDeRose/derxjs" rel="noopener noreferrer"&gt;@derxjs OSS packages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Senior Engineer and Engineering Manager at &lt;a href="https://nrwl.io/" rel="noopener noreferrer"&gt;Nrwl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checkout out my &lt;a href="https://zackderose.dev/videos" rel="noopener noreferrer"&gt;personal website&lt;/a&gt; for more of my dev content! And go bug &lt;a href="https://twitter.com/jeffbcross" rel="noopener noreferrer"&gt;Jeff Cross&lt;/a&gt;/&lt;a href="https://twitter.com/joerjohnson" rel="noopener noreferrer"&gt;Joe Johnson&lt;/a&gt; if you want to hire me to come help out your codebase or come help level up your team on Nx/NgRx/DeRxJS/RxJS/State Management! (I especially love building awesome stuff - and building up teams with bright developers that are eager to learn!)&lt;/p&gt;

</description>
      <category>rxjs</category>
      <category>typescript</category>
      <category>tdd</category>
      <category>nx</category>
    </item>
    <item>
      <title>Creating a Habit Tracker App using Typescript, React, and Tailwind</title>
      <dc:creator>Zack DeRose</dc:creator>
      <pubDate>Sun, 21 Feb 2021 07:46:53 +0000</pubDate>
      <link>https://dev.to/zackderose/creating-a-habit-tracker-app-using-typescript-react-and-tailwind-183c</link>
      <guid>https://dev.to/zackderose/creating-a-habit-tracker-app-using-typescript-react-and-tailwind-183c</guid>
      <description>&lt;h1&gt;
  
  
  What We're Building!
&lt;/h1&gt;

&lt;p&gt;In a previous article, I had mentioned &lt;a href="https://dev.to/zackderose/resolution-update-february-2021-284m#building-on-systemization"&gt;creating a 'morning habit stack'&lt;/a&gt; as part of my on-going effort this year to establish good habits and become a more productive person.&lt;/p&gt;

&lt;p&gt;As a part of wanting to establish this habit, I figured I'd use the "Seinfeld Calendar" approach - but being a web developer, I'd kinda prefer to make some sort of online tool for tracking my habit... so let's do it!!&lt;/p&gt;

&lt;p&gt;[Most of my efforts are fueled by the book &lt;a href="https://www.amazon.com/Atomic-Habits-James-Clear-audiobook/dp/B07RFSSYBH/ref=sr_1_1?dchild=1&amp;amp;keywords=atomic+habits+james+clear&amp;amp;qid=1613540679&amp;amp;sr=8-1" rel="noopener noreferrer"&gt;"Atomic Habits" by James Clear&lt;/a&gt; - if you're interested in learning more!!]&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%2Fqdopn9u47ywxjzd5njux.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%2Fqdopn9u47ywxjzd5njux.png" alt="How it will look!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Breaking Down the Problem
&lt;/h1&gt;

&lt;p&gt;A critical skill [maybe THE critical skill] in our line of work is breaking down a problem into logical pieces. Let's follow this exercise for this small example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Determine the 'shape' of our data, both in how we'd like to "store" the data, and how we'd like to use the data within a &lt;code&gt;Calendar&lt;/code&gt; component.&lt;/li&gt;
&lt;li&gt;Create functions to convert the "stored" shape of our data to the shape we'd like to use in our components.&lt;/li&gt;
&lt;li&gt;Create a React Component for our Calendar, with appropriately accessible HTML elements (good bones are important!!)&lt;/li&gt;
&lt;li&gt;Add tailwind utility classes to improve our presentation!&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  First Step: Determine the "Shape" of our Data!
&lt;/h1&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%2Fqdopn9u47ywxjzd5njux.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%2Fqdopn9u47ywxjzd5njux.png" alt="How it will look!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at our desired output, let's focus first on determining the way we'd like to store data for our calendar. I think there are likely many valid ways we could take this - but going with a basic and simple approach, I think the following Typescript interface covers most of our bases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;Given this information, we &lt;em&gt;should&lt;/em&gt; be able to determine everything we'd need to display the calendar view shown at the start of this section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Types, plus Iterables!
&lt;/h2&gt;

&lt;p&gt;To enhance this typing, we can add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;DAYS_OF_THE_WEEK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sunday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Monday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tuesday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wednesday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thursday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Friday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saturday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;daysOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;January&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;February&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;March&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;April&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;May&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;June&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;July&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;August&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;September&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;October&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;November&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;December&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;as const&lt;/code&gt; syntax here signals to our typing system that these arrays are &lt;code&gt;readonly&lt;/code&gt;, which allows us to create a union type from the arrays!&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%2Fuploads%2Farticles%2Fl5r20r0itfgm2hrk8669.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%2Fuploads%2Farticles%2Fl5r20r0itfgm2hrk8669.png" alt="Displaying Intellisence on the DayOfTheWeek type."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is helpful as it gives us a proper type, as well as an Iterable, that we'll see come in handy in future sections!&lt;/p&gt;

&lt;p&gt;Let's also define a union type for our results to make that a bit more clear-cut:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HabitResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these changes, we can now enhance our typing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CalendarProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HabitResult&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;h2&gt;
  
  
  Data Format for our "Template"
&lt;/h2&gt;

&lt;p&gt;The data model we've set up is simple enough for storage I'd wager! It's pretty minimal in what it holds (we probably &lt;em&gt;could&lt;/em&gt; remove the day of the week and add a year, and then extrapolate the day of the week from the other info... but this works for our use-case I think). The data is fairly human-readable as well, while still passing the 5-second rule of comprehension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"startDate"&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;"month"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"February"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dayOfTheWeek"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thursday"&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;"endDate"&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;"month"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"March"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dayOfTheWeek"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sunday"&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;"results"&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="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"success"&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="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;When it comes to the data we'd like to work with in 'templating' out our calendar component in &lt;code&gt;tsx&lt;/code&gt;, we'd like to refine this data a bit to make it easier to work with! Here's what I'd like to see personally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Sunday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;January&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out of bounds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&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;If this were fully expanded, it would definitely start surpassing the capacities of a human brain (well, mine at least!) but its perfect data format for our computers to iterate through as we create our DOM nodes!&lt;/p&gt;

&lt;p&gt;To type this properly, let's reach for the &lt;a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype" rel="noopener noreferrer"&gt;&lt;code&gt;Record&lt;/code&gt;&lt;/a&gt; utility type from Typescript. I definitely advise reading the official docs here! But the short version is a &lt;code&gt;Record&amp;lt;keyType, valueType&amp;gt;&lt;/code&gt; will specify an object where all the keys conform to the &lt;code&gt;keyValue&lt;/code&gt; and all &lt;code&gt;value&lt;/code&gt;s conform to the &lt;code&gt;valueType&lt;/code&gt;, AND FURTHERMORE - if the &lt;code&gt;keyType&lt;/code&gt; is a union type, then it will assert that a key exists for EVERY type in the union type!&lt;/p&gt;

&lt;p&gt;This is perfect for our 'days of the week' use-case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerDay&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResultToDisplay&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerWeek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerDay&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also looking at the &lt;code&gt;ResultToDisplay&lt;/code&gt; type, we'd like for this to support all possibilities of the &lt;code&gt;HabitTrackerResult&lt;/code&gt;, but we also probably need a &lt;code&gt;out of bounds&lt;/code&gt; and a &lt;code&gt;no result yet&lt;/code&gt; option here to support everything our UI requires! To do this, lets create that type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ResultToDisplay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerResult&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out of bounds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no result yet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a week, let's create the following type for all our data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerWeek&lt;/span&gt;&lt;span class="o"&gt;&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;This will allow us to create an object with our week names mapped to a week's chunk of data. To supplement this data, we'll also probably want a list of all week names to iterate though. We &lt;em&gt;could&lt;/em&gt; create this from this object (&lt;code&gt;Object.keys(habitTrackerData)&lt;/code&gt;), but might as well supply it to our template, so as to keep it as simple as possible. We'll also want the streak information! This can be determined only by the &lt;code&gt;HabitResult&lt;/code&gt; array, but we'll throw it all together to give us the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerTemplateData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;weekNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxSuccessStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've hardly written anything in the way of implementation at this point, but we now have a solid data model that we're expressing in our TypeScript code! The rest will start to fall into place at this point!&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 2: Converting From &lt;code&gt;CalendarProps&lt;/code&gt; to &lt;code&gt;HabitTrackerTemplateData&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Let's start with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createHabitTrackerTemplateData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarProps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerTemplateData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, here's the cool thing about our solution - at this point, we definitely could skip ahead to steps 3 &amp;amp;&amp;amp; 4 and leave this unimplemented! (Maybe have it return an example of the desired data)&lt;/p&gt;

&lt;p&gt;This is all benefit of the 'work' we did in step 1 to set up our data models. Since we're here though, we might as well set up the problem.&lt;/p&gt;

&lt;p&gt;Since we'd like to have general confidence in our solution, we might as well start with a unit test to assert that our implementation on this function is correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;CalendarProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createHabitTrackerTemplateData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;HabitTrackerTemplateData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./calendar.utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TestParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarProps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerTemplateData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testCreatingTemplateData&lt;/span&gt;&lt;span class="p"&gt;(&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;output&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;TestParams&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;createHabitTrackerTemplateData&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createHabitTrackerTemplateData()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;known example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;testCreatingTemplateData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;startDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;February&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thursday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;endDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;March&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sunday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&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="na"&gt;weekNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-7&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* much too big to show here */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;maxSuccessStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give us a red/green check to run while we fill out our implementation!&lt;/p&gt;

&lt;p&gt;When it comes to our actual implementation, let's start with the streak information. Streak info is a function of the results array, so we can create a smaller piece of functionality that focuses only on this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;determineStreakInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HabitResult&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;maxSuccessStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&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;let&lt;/span&gt; &lt;span class="nx"&gt;maxSuccessStreak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HabitResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;for &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;result&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;currentStreak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxSuccessStreak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;maxSuccessStreak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentStreak&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxSuccessStreak&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;Next, we'll need to build our &lt;code&gt;HabitTrackerData&lt;/code&gt; object. Thinking through this problem, the general algorithm here would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;start with a pointer at the provided first day&lt;/li&gt;
&lt;li&gt;create a 'reverse' pointer and loop backwards one day at a time until you hit a 'Sunday' (the first day of the week), adding 'out of bounds' days to our object as we go.&lt;/li&gt;
&lt;li&gt;Go back to our original pointer, and advance this pointer forward one day at a time until we hit the provided end day, adding either the data from the provided results array, or 'no result yet' if the array isn't large enough to include the given day.&lt;/li&gt;
&lt;li&gt;Continue to advance the pointer one day at a time, until we hit a 'Saturday' (last day of the week), adding 'out of bounds' days to our object as we go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the while, keeping a record of the # week we're on, and advancing that when the pointer goes from a 'Saturday' to a 'Sunday'.&lt;/p&gt;

&lt;p&gt;This is a fairly messy implementation (most implementations involving dates are!) but we can make it happen! Let's start with some utilities we know we'll need based on this implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a function that takes a &lt;code&gt;CalendarDate&lt;/code&gt; and returns the previous &lt;code&gt;CalendarDate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a function that takes a &lt;code&gt;CalendarDate&lt;/code&gt; and returns the next &lt;code&gt;CalendarDate&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create these properly, we'll also need a map of the number of days per month, as it will affect the &lt;code&gt;date&lt;/code&gt; when going backwards, and when to switch to the next month when going forwards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;daysPerMonth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;January&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;February&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;March&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;April&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;May&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;June&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;July&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;August&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;September&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;October&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;November&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;December&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nextMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;December&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;January&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nextDayOfWeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saturday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sunday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;daysOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;daysOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nextCalendarDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;daysPerMonth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nextMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nextDayOfWeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nextDayOfWeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;previousMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;January&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;December&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;previousDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;daysPerMonth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;previousMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;previousDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sunday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saturday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;daysOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;daysOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;previousCalendarDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;CalendarDate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;previousDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;previousDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;previousMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;calendarDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As complex as this is already - we're still not accommodating for leap years... I'm not going to sweat it for now, but! We could in the future (maybe for 2024!) adjust our map of  months to days in the month to instead point to a function that returns a number - the idea being that the function would take the year as a parameter, and we could then use the Gregorian calendar logic to determine the proper number of days for February based on that (the function for all other months would ignore any parameters and return the value they current point to).&lt;/p&gt;

&lt;p&gt;And now my implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createHabitTrackerTemplateData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;startDay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;endDay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;CalendarProps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;HabitTrackerTemplateData&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;weekNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// go backwards until you hit a 'Sunday'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstWeekOutOfBoundsDates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;startDay&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sunday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;previousCalendarDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;firstWeekOutOfBoundsDates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstWeekPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out of bounds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// go forwards day by day, populating from the provided&lt;/span&gt;
  &lt;span class="c1"&gt;// `results` array, until you hit the provided `endDay`&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DisplayResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;week-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;firstWeekOutOfBoundsDates&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dayIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;startDay&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;weekCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;endDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;endDay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`week-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weekCounter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dayIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no result yet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;dayPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextCalendarDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;dayIndex&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sunday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;weekCounter&lt;/span&gt;&lt;span class="o"&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;newWeekName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`week-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weekCounter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;weekNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newWeekName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;newWeekName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`week-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weekCounter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dayIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no result yet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// keep going forwards until you hit a `Saturday`&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saturday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;dayPointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextCalendarDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`week-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weekCounter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dayPointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out of bounds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;weekNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;determineStreakInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not crazy with this implementation - it certainly doesn't pass a 5 second rule (maybe not even a 5 minute rule...) but our test is going green with this in place, which gives me the general confidence to proceed.&lt;/p&gt;

&lt;p&gt;The cool thing here is we have some great general utility functions available now - that actually could just as easily be used in Angular or any other JS framework!&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 3: Creating a React Component
&lt;/h1&gt;

&lt;p&gt;Breaking down our React Component, we'll want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;define our props as the &lt;code&gt;HabitTrackerProps&lt;/code&gt; type we created in the first part&lt;/li&gt;
&lt;li&gt;call our &lt;code&gt;createHabitTrackerTemplateData()&lt;/code&gt;, passing in those props, and destructuring out the properties&lt;/li&gt;
&lt;li&gt;create our component template in &lt;code&gt;tsx&lt;/code&gt;, by &lt;code&gt;map()&lt;/code&gt;ing over all weeknames, and then inside of that &lt;code&gt;map()&lt;/code&gt;ing over all days of the week, creating a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; for each day&lt;/li&gt;
&lt;li&gt;if the day was a 'success', set the background image to that &lt;code&gt;div&lt;/code&gt; to the url of a green check - or a red x if it was a 'failure'.&lt;/li&gt;
&lt;li&gt;Add streak information at the bottom of all these divs!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's how that looks in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GREEN_CHECK_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some_image_url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RED_X_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some_image_url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Calendar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;weekNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;maxSuccessStreak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHabitTrackerTemplateData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;weekNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;weekName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;daysOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;weekName&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
                &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weekName&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="nx"&gt;dayOfTheWeek&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;backgroundImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`url(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
                    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;GREEN_CHECK_URL&lt;/span&gt;
                      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;RED_X_URL&lt;/span&gt;
                      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="na"&gt;backgroundSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Max Success Streak: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;maxSuccessStreak&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Max Failure Streak: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;maxFailureStreak&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 4: Add Tailwind Styles
&lt;/h1&gt;

&lt;p&gt;At this point, we've got solid bones to our html, but actually a relatively un-usable presentation so far. We'll use Tailwind as a style system to get this up to at least passable quickly!&lt;/p&gt;

&lt;p&gt;Here's the highlights of the tailwind goals for this component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a 7-column grid - to present our calendar&lt;/li&gt;
&lt;li&gt;make the size of our &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;'s reactive to the screen size, by setting a small default size, but increasing this (with the &lt;code&gt;md:&lt;/code&gt; modifier) once the screen size passes the "medium" threshold&lt;/li&gt;
&lt;li&gt;Add borders to our day &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;'s - making them twice as thick on the edges to make the display consistant.&lt;/li&gt;
&lt;li&gt;Add rounded corners to the borders on the corners of our calendar&lt;/li&gt;
&lt;li&gt;Put the inner &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with our # date in the top-left of the day &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and give it a circle-appearance.&lt;/li&gt;
&lt;li&gt;Center the streak information headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the source code of this stackblitz for the details on the implementation!&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/typescript-react-tailwind-habit-tracker?view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion &amp;amp;&amp;amp; Potential Next Steps
&lt;/h1&gt;

&lt;p&gt;And there we go! From nothing to something semi-cool :). As a developer with limited React experience, I'm pretty enthused in general regarding the simplicity of React.&lt;/p&gt;

&lt;p&gt;An obvious next step would be to read our &lt;code&gt;HabitTrackerProps&lt;/code&gt; data from some network call - or even better to push change events from the server to our client! I've got some ideas for this in the pipes...&lt;/p&gt;

&lt;p&gt;Another interesting way to take this further would be to introduce Nx to the project. Currently, I'm intentionally using &lt;code&gt;create-react-app&lt;/code&gt; to try and understand the general "React way" of doing things. But being able to introduce this code (especially the &lt;code&gt;calendar.utils.ts&lt;/code&gt; file) would be great in terms of pretty easily getting up an Angular version of this component!&lt;/p&gt;

&lt;p&gt;Also cool would be to share the entire React component as well - making it available for me to run it in a stand-alone app, but also bring it into my other sites as needed!&lt;/p&gt;

</description>
      <category>react</category>
      <category>tutorial</category>
      <category>video</category>
    </item>
  </channel>
</rss>
