<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: ‹div›RIOTS</title>
    <description>The latest articles on Forem by ‹div›RIOTS (@divriots).</description>
    <link>https://forem.com/divriots</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%2Forganization%2Fprofile_image%2F5015%2Fbd1d2131-94ae-4069-859f-95fca616e1c3.png</url>
      <title>Forem: ‹div›RIOTS</title>
      <link>https://forem.com/divriots</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/divriots"/>
    <language>en</language>
    <item>
      <title>Why devs should build with Design Systems</title>
      <dc:creator>RBerthier</dc:creator>
      <pubDate>Wed, 09 Feb 2022 09:53:07 +0000</pubDate>
      <link>https://forem.com/divriots/why-devs-should-build-with-design-systems-34ck</link>
      <guid>https://forem.com/divriots/why-devs-should-build-with-design-systems-34ck</guid>
      <description>&lt;p&gt;Design systems are now a hot topic among front-end teams. Design Systems have switched from best-in-class solutions for big enterprises to must-have for any front-end team.&lt;/p&gt;

&lt;p&gt;Initially, Design Systems have been popularized by designers (with tools like Figma/Sketch), to help them build reusable components to improve their work efficiency and consistency.&lt;/p&gt;

&lt;p&gt;From a developer perspective, Design Systems could somehow be compared to Components Libraries, as they are widely used as reusable components sources to build web products. So why should developers consider building with a Design System on the code side instead?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Design Systems are not just components libraries. They also include design tokens, documentation, and a design kit. Check our article &lt;a href="https://backlight.dev/mastery/what-is-a-design-system"&gt;What is a Design System ?&lt;/a&gt; if you want to learn more about this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s discover some key benefits of building a Design System for developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤝 Workspace for collaboration
&lt;/h2&gt;

&lt;p&gt;One of the most important benefits of working with a Design System is to set up an isolated and collaborative environment to ensure the quality and the adoption of all the assets (tokens, components, documentation, designs …) that are built and consumed by the team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--78LcK-al--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/workspace.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--78LcK-al--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/workspace.png" alt="workspace.png" width="880" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A good Design System platform should provide core features to foster and streamline collaboration within the developer team, but also with the whole front-end team.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For instance, &lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt; offers real-time preview and collaboration, branch/pull-request management, asynchronous messaging, visual reviews, …&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧩 Benefits from design tokens
&lt;/h2&gt;

&lt;p&gt;Design tokens are core elements of the design language used both by designers and developers to build components in Design Systems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We can refer to tokens as "constants" hosting common values in the Design System codebase. An interesting thread about it &lt;a href="https://twitter.com/equinusocio/status/1481283030903836678"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MKGa3eDG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/tokens.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MKGa3eDG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/tokens.jpg" alt="tokens.jpg" width="880" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For instance, here are some common tokens: colors, fonts, spacing, border, radius, opacity, shadows, z-index, …&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Design System is a source of truth for tokens, built-in collaboration with designers and developers. All Design System components built on top of tokens benefit from auto updates when tokens are modified.&lt;/p&gt;

&lt;p&gt;So having a Design System, instead of a standalone component library, makes it easier and more efficient to build consistent and maintainable components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VVP2KmUQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/combination.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VVP2KmUQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/combination.png" alt="combination.png" width="880" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tips: Design system tokens on the code-side can be synchronized with the Design System on the design-side thanks to some dedicated tools (like &lt;a href="https://specifyapp.com/"&gt;Specify&lt;/a&gt;) or built-in features in Design System tools (like &lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📦 Benefits from a component library inside a Design System
&lt;/h2&gt;

&lt;p&gt;Component Libraries (like Material UI) have become very popular among developers, as they help speed up the development and bring more reusability/consistency. The challenge of building with component libraries lies in the customization it requires to fit specific design guidelines.&lt;/p&gt;

&lt;p&gt;Building a custom component library inside a Design System either from scratch or on top of a popular component library, can have great benefits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4SYYlE8a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/nested.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4SYYlE8a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/nested.png" alt="nested.png" width="880" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Design Systems are natively built to offer a nested environment to split the work on components from the work on products. Using a Design System can help prevent developers from customizing components directly in production code, which could lead to consistency and maintenance issues in the long run. Developers are working on components solely in the Design System, while consuming components in production code. It makes customization of components easier to manage and ensure better quality over time.&lt;/p&gt;

&lt;p&gt;Building and updating the components library in the Design Systems will also benefit from collaboration reviews and testing workflows, before being shipped in production code. It improves the reliability of new releases and automates components maintenance in production. Design Systems are even more powerful when working in cross-products or multi-brands environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b0_rmrGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/components.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b0_rmrGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/components.jpg" alt="components.jpg" width="880" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For instance, &lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt; provides a full environment to work solely on Design Systems, with Github/Gitlab repository versioning and NPM package publishing features to release bullet-proof assets ready-to-use in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having the component library inside the Design System also offers the possibility to enhance documentation with live components and playgrounds directly integrated with code snippets from the component library. It makes documentation easier to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 Benefits from design kits inside developer Design System
&lt;/h2&gt;

&lt;p&gt;A Design System also provides design kits assets (brand guidelines, brand assets, component designs, …). In a Design System on the developer side, a design kit could be a mapping with the Design System on the design side, either through links to related design assets (Figma/Sketch files for instance) or with some synchronization (provided by design-to-code / code-to-design features).&lt;/p&gt;

&lt;p&gt;The developer team can benefit from a Design System with such design kits, as it will help tackle the handoff issue with designers. As the Design System provides design for each component it makes it easy for developers to always have the last related design version at hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9uqS1XB_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/designkits.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9uqS1XB_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/designkits.jpg" alt="designkits.jpg" width="880" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt; offers the capability to add design links to each component of the Design System.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📒 Benefits from documentation for developers
&lt;/h2&gt;

&lt;p&gt;Documentation is a core element of a Design System. This is a collaborative workspace where the whole front-end team shares basics, guidelines, and how-to on the design and code.&lt;/p&gt;

&lt;p&gt;It helps foster collaboration and information sharing across the team. Documentation is a powerful tool for developers to find information on how to use design tokens and the component library. It’s especially useful when onboarding new developers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RqGwwn_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/documentation.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RqGwwn_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://backlight.dev/img/blog/why-should-developers-build-with-design-systems/documentation.jpg" alt="documentation.jpg" width="880" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt; has built-in capability to build collaborative and live documentation from Design System components and tokens.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚡️ Boost developers efficiency
&lt;/h2&gt;

&lt;p&gt;As for designers, developers benefit from a great boost of productivity when building products with Design Systems. Reusable components from Design Systems help save valuable time from developers each time they need to use components (not reinventing the wheel, or wasting time finding the right component).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://sparkbox.com/foundry/design_system_roi_impact_of_design_systems_business_value_carbon_design_system"&gt;This study&lt;/a&gt; shows for instance that it’s 47% faster to build with a Design System instead of building from scratch, with also better consistency in the end.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚧 Lower code maintenance
&lt;/h2&gt;

&lt;p&gt;Working with a Design System on the developer side also means saving loads of time to ensure product consistency and maintenance. When new requirements are coming from the design team (like changing a primary color or a component design/behavior …), it can be quite tedious and time-consuming to upgrade all assets in production when not using a Design System. On the other hand, a Design System offers a collaborative environment to develop components safely, by being able to build, upgrade and test them  in isolation through multiple contexts/conditions, making later product development and maintenance lighter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt;, just update your token/component, create a pull-request, merge, ship an updated npm package, and voila all your related components in production are updated automatically. No need to waste time maintaining components in production! 🧙‍♂️&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎯 Better consistency
&lt;/h2&gt;

&lt;p&gt;Using components from Design Systems will ensure that production components are consistent, as the components should be loaded and updated directly from the Design System. All similar components are strictly the same and all basic tokens are bound with the Design System.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⭐️ Better quality and accessibility
&lt;/h2&gt;

&lt;p&gt;Quality and accessibility are key aspects, with technical challenges to tackle. Building components in a nested environment, like in a Design System, is a good way to ensure that components are well crafted and tested collaboratively, before being safely shipped in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  😀 Conclusion
&lt;/h2&gt;

&lt;p&gt;Building with a Design System can have multiple benefits for developers. Eventually, the Design System will become a source of truth that is a source of trust for the whole team. Design Systems ultimately empower developers with collaboration, speed, quality, and consistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to start playing with Design Systems, give a try to one of the starter kits provided by &lt;a href="https://backlight.dev/"&gt;Backlight&lt;/a&gt;. Many frameworks are supported, coming with loads of tokens and components ready to use. Perfect for a quick jump-in.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>front</category>
      <category>designsystem</category>
      <category>productivity</category>
      <category>ux</category>
    </item>
    <item>
      <title>A story of how we migrated to pnpm</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 26 Jan 2022 09:13:13 +0000</pubDate>
      <link>https://forem.com/divriots/a-story-of-how-we-migrated-to-pnpm-59b3</link>
      <guid>https://forem.com/divriots/a-story-of-how-we-migrated-to-pnpm-59b3</guid>
      <description>&lt;p&gt;It all started with me trying to improve our Continuous Integration pipeline. I'm a strong believer in having proper CI - the threshold for how much to invest in unit &amp;amp; integration tests is always tricky to set, but to me the bare minimum should be to have linting and type checking run on every commit.&lt;/p&gt;

&lt;p&gt;Now, having that bare minimum is great, but it also needs to be as fast as possible. When you want commits &amp;amp; reviews to be fast, CI cannot be the one thing holding you back.&lt;/p&gt;

&lt;p&gt;Yet... This is what we would see in the best case scenario on that bare minimum linting &amp;amp; type checking job:&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%2Fgzf7b10fa7fbtof5uqf2.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%2Fgzf7b10fa7fbtof5uqf2.jpg" alt="yarn 2 install with cache taking 1 minute and 11 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1 minute and 11 seconds just to install dependencies. Obviously, the job has to do more afterwards, and that's where I'd prefer it to spend time.&lt;/p&gt;

&lt;p&gt;But wait, there's more. This was the best case scenario. You may know that package managers have caches, and a known trick to speed installs is to save that cache after CI runs, so it can be reused for subsequent runs. An easy way to do that nowadays is to use &lt;a href="https://github.com/actions/setup-node#caching-packages-dependencies" rel="noopener noreferrer"&gt;actions/node-setup's caching capabilities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However the cache cannot always be used. As soon as the lock file changes, typically when adding dependencies, the cache isn't reused &lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;because the cache's hash is usually computed based on the lock file&lt;/a&gt;. We would then get:&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%2Fn1yka18150aey2uvu0vb.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%2Fn1yka18150aey2uvu0vb.jpg" alt="yarn 2 install without cache taking 6 minutes and 31 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6 minutes and 31 seconds 🐌.&lt;br&gt;
That's when we really thought we needed to do something.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where we stood with Yarn
&lt;/h2&gt;

&lt;p&gt;We have been using Yarn 2 for quite some time, having originally switched to it for its native workspace support which is great for monorepos as we happen to have one. Because we use a lot of different dev tools (in no particular order - &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, &lt;a href="https://vitepress.vuejs.org/" rel="noopener noreferrer"&gt;Vitepress&lt;/a&gt;, &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt;, &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;, &lt;a href="https://www.11ty.dev/" rel="noopener noreferrer"&gt;Eleventy&lt;/a&gt;, &lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;Firebase tools&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;...) and many more actual dependencies. It's easy to understand how many dependencies we're bound to have when you see all the frameworks we support, whether &lt;a href="https://webcomponents.dev/docs/technologies" rel="noopener noreferrer"&gt;on WebComponents.dev&lt;/a&gt; or &lt;a href="https://backlight.dev/docs/technologies" rel="noopener noreferrer"&gt;on Backlight&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You may know Yarn 2 for introducing the &lt;a href="https://yarnpkg.com/features/pnp" rel="noopener noreferrer"&gt;Plug'n'Play linker&lt;/a&gt;. To make it short, it completely forfeits the idea of the &lt;code&gt;node_modules&lt;/code&gt; resolution mechanism and tells Node to depend on Yarn for dependency resolution.&lt;br&gt;
It's a really interesting idea but dropping &lt;code&gt;node_modules&lt;/code&gt; is a compatibility challenge which kept us away from trying it. We stuck and are sticking to &lt;code&gt;node_modules&lt;/code&gt; for now.&lt;/p&gt;

&lt;p&gt;Anyway, because &lt;a href="https://dev.to/arcanis/yarn-3-0-performances-esbuild-better-patches-e07"&gt;Yarn 3 had been released for a few months with performances improvements&lt;/a&gt;, we decided to give it try to see if that would speedup our builds.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trying yarn 3
&lt;/h2&gt;

&lt;p&gt;Upgrading to Yarn 3 is fairly simple:&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; yarn &lt;span class="nb"&gt;set &lt;/span&gt;version berry

➤ YN0000: Retrieving https://repo.yarnpkg.com/3.1.1/packages/yarnpkg-cli/bin/yarn.js
➤ YN0000: Saving the new release &lt;span class="k"&gt;in&lt;/span&gt; .yarn/releases/yarn-3.1.1.cjs
➤ YN0000: Done &lt;span class="k"&gt;in &lt;/span&gt;0s 758ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we go, we were upgraded to Yarn 3.&lt;/p&gt;

&lt;p&gt;I'll spare you another pair of screenshots, but that got us down a bit, to 4 minutes 50 seconds without cache and 57 seconds with cache.&lt;/p&gt;

&lt;p&gt;I'm sparing you the screenshots for a good reason - I did mention we have been using Yarn 2 in that monorepo for a while. We've also been adding so many packages in different workspaces that we ended up with a lot of duplicated dependencies, ie with multiple versions of the same packages.&lt;/p&gt;

&lt;p&gt;So just for the sake of the comparison and because our original point was to speedup install times, I went ahead and completely removed the &lt;code&gt;yarn.lock&lt;/code&gt; file and tested again.&lt;/p&gt;

&lt;p&gt;With cache, down to 50 seconds:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsaay7z3cgaof6k6sdxp.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%2Fxsaay7z3cgaof6k6sdxp.jpg" alt="yarn 3 install with cache taking 50 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And without cache, we got down to 4 minutes and 1 second:&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%2Fjalqb19f0yitcpms3xml.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%2Fjalqb19f0yitcpms3xml.jpg" alt="yarn 3 install without cache taking 4 minutes and 1 second"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's fair to say we've sped up our builds quite a lot already, but we wanted to go further yet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now, that's where we decided to try out &lt;em&gt;pnpm&lt;/em&gt;. But &lt;a href="https://twitter.com/larixer/status/1483513893825781766" rel="noopener noreferrer"&gt;as pointed out by @larixer&lt;/a&gt;, there are also options you can set to speedup Yarn. We tried them out to make it a fairer comparison.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/larixer"&gt;@larixer&lt;/a&gt; mentions the 3 following options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nmMode: hardlinks-global
enableGlobalCache: true
compressionLevel: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And they do help a lot, especially without cache where we go down to 1 minute 10 seconds:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6etd9qayk5g0f3ndq4m.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%2Fi6etd9qayk5g0f3ndq4m.jpg" alt="yarn 3 optimized install without cache taking 1 minute and 1O seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also slightly faster with a cache, yielding 45 seconds:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18a5b4vgp0rc8krhqzdx.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%2F18a5b4vgp0rc8krhqzdx.jpg" alt="yarn 3 install with cache taking 45 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So if you are running Yarn, do considering trying them out! Chances are they will greatly improve your install times.&lt;/p&gt;

&lt;p&gt;Anyhow, let's jump into pnpm!&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter pnpm
&lt;/h2&gt;

&lt;p&gt;pnpm stands for &lt;strong&gt;Performant NPM&lt;/strong&gt;. Its adoption has been really steady as its close to the 15k stars at the moment on Github. It also comes with out of the box support for workspaces, making it easier for us to consider.&lt;/p&gt;

&lt;p&gt;As its name indicates, it &lt;a href="https://pnpm.io/motivation" rel="noopener noreferrer"&gt;really emphasizes performances&lt;/a&gt;, both regarding disk space and installation times. In all the figures provided, whether &lt;a href="https://pnpm.io/benchmarks" rel="noopener noreferrer"&gt;from pnpm&lt;/a&gt; or &lt;a href="https://p.datadoghq.eu/sb/d2wdprp9uki7gfks-c562c42f4dfd0ade4885690fa719c818?theme=dark&amp;amp;tpl_var_npm=%2A&amp;amp;tpl_var_yarn-classic=%2A&amp;amp;tpl_var_yarn-modern=%2A&amp;amp;tpl_var_yarn-nm=%2A&amp;amp;tpl_var_yarn-pnpm=no&amp;amp;tv_mode=false" rel="noopener noreferrer"&gt;from Yarn&lt;/a&gt; you can see that pnpm really comes out faster most of the time.&lt;/p&gt;

&lt;p&gt;There seems to be two main reasons for it.&lt;/p&gt;

&lt;p&gt;One, being performance-oriented, its implementation targets speed. You may have seen when installing with &lt;em&gt;yarn&lt;/em&gt; or &lt;em&gt;npm&lt;/em&gt; timings for each of the resolution/fetch/link steps. It appears that &lt;em&gt;pnpm&lt;/em&gt; is not doing those steps sequentially globally, but sequentially for each package in parallel which explains why it's so efficient.&lt;/p&gt;

&lt;p&gt;The other reason is the way it deals with the &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;
&lt;h3&gt;
  
  
  Centralized addressable cache
&lt;/h3&gt;

&lt;p&gt;pnpm calls it a &lt;em&gt;content addressable filestore&lt;/em&gt;, and we know other package managers like &lt;em&gt;yarn&lt;/em&gt; or &lt;em&gt;npm&lt;/em&gt; have caches as well, which allow you not to have to re-download.&lt;/p&gt;

&lt;p&gt;The difference with pnpm's is that this cache is also referenced by your node_modules files, which are effectively hard-links to that cache. A hard-link means your OS will report those files as being actual files - but they're not. So the actual disk usage occurs in pnpm's cache, not in your node_modules folder. You save space, and installation time, because there's way less IO involved in setting up that infamous node_modules folder! 🪄&lt;/p&gt;
&lt;h3&gt;
  
  
  Non-flat node_modules
&lt;/h3&gt;

&lt;p&gt;What's also interesting is the way the node_modules is organized with pnpm. npm and yarn (when using the node_modules linker) tend to do hoisting to save space as they're not using links. Hoisting is the act of installing a dependency in a parent directory rather than where it is depended on. So if you have a dependency that can be resolved to the same version pulled by two other packages, they'll try to hoist that dependency to avoid storing that same dependency twice in your node_modules.&lt;/p&gt;

&lt;p&gt;The behavior of pnpm is different, somewhat more consistent. It's always setting up the node_modules structure the same way. First, it's non-flat. So running &lt;code&gt;pnpm install vite&lt;/code&gt; in an empty folder will result in the following node_modules:&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; tree node_modules &lt;span class="nt"&gt;-L&lt;/span&gt; 1
node_modules
└── vite -&amp;gt; .pnpm/vite@2.7.10/node_modules/vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So our node_modules only contains vite and not all of its dependencies. This may seem unusual, but this avoids &lt;strong&gt;phantom dependencies&lt;/strong&gt;. Phantom dependencies are dependencies that you end up being able to use without explicitly depending on them. This is a rather dangerous practice, because you do not control those - you may update the original dependency, just upgrading it to a new patch, but its dependencies may have been upgraded to major versions breaking your own code!&lt;/p&gt;

&lt;p&gt;In our previous example, my source code won't be able to require any other dependency but &lt;code&gt;vite&lt;/code&gt; as it's the only one which was effectively installed to the top of my node_modules.&lt;/p&gt;

&lt;p&gt;Now we can see that this folder is actually linking to another folder in &lt;code&gt;node_modules​/.pnpm&lt;/code&gt;: this is pnpm's &lt;em&gt;Virtual Store&lt;/em&gt; where you will find all the packages installed in your project.&lt;/p&gt;

&lt;p&gt;If we take a peek at this folder:&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; tree node_modules/.pnpm/vite@2.7.10 &lt;span class="nt"&gt;-L&lt;/span&gt; 2
node_modules/.pnpm/vite@2.7.10
└── node_modules
    ├── esbuild -&amp;gt; ../../esbuild@0.13.15/node_modules/esbuild
    ├── postcss -&amp;gt; ../../postcss@8.4.5/node_modules/postcss
    ├── resolve -&amp;gt; ../../resolve@1.21.0/node_modules/resolve
    ├── rollup -&amp;gt; ../../rollup@2.63.0/node_modules/rollup
    └── vite
        ├── bin
        ├── CHANGELOG.md
        ├── client.d.ts
        ├── dist
        ├── LICENSE.md
        ├── node_modules
        ├── package.json
        ├── README.md
        ├── src
        └── types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, vite itself and its dependencies were installed to &lt;code&gt;node_modules/​.pnpm/​vite@2.7.10/​node_modules&lt;/code&gt;.&lt;br&gt;
The magic that makes it all work is that Node, when resolving packages, considers the target of the symlink instead of using the symlink's path itself. So when I do &lt;code&gt;require('vite')&lt;/code&gt; from an &lt;code&gt;src/​index.js&lt;/code&gt; file, Node finds the &lt;code&gt;node_modules/​vite&lt;/code&gt; file by iterating on parent directories looking for a &lt;code&gt;node_modules&lt;/code&gt; folder containing &lt;code&gt;vite&lt;/code&gt; but actually resolves it to the source of the symlink:&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; node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"console.log(require.resolve('vite'))
/tmp/foobar/node_modules/.pnpm/vite@2.7.10/node_modules/vite/dist/node/index.js
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means that any further package resolutions needed will effectively be done from this folder - so if that &lt;code&gt;/tmp/​foobar/​node_modules/​.pnpm/​vite@2.7.10/​node_modules/​vite/​dist/​node/​index.js&lt;/code&gt; file requires &lt;code&gt;esbuild&lt;/code&gt; it will find it in &lt;code&gt;node_modules/​.pnpm/​vite@2.7.10/​node_modules/​esbuild&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;This is also why some dependencies don't play nice with pnpm: because they don't resolve symlink targets. But we'll get to that later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: peer dependencies have a special handling in pnpm, if you're interested about it I suggest &lt;a href="https://pnpm.io/how-peers-are-resolved" rel="noopener noreferrer"&gt;this read&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have a rough understanding of how pnpm works, let's try using it! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating to pnpm
&lt;/h2&gt;

&lt;h3&gt;
  
  
  pnpm import
&lt;/h3&gt;

&lt;p&gt;pnpm comes with a command to import yarn's locked dependencies:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pnpm.io/cli/import" rel="noopener noreferrer"&gt;https://pnpm.io/cli/import&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's just one gotcha when you're using it in a monorepo: &lt;strong&gt;the workspaces have to be declared&lt;/strong&gt; in your &lt;a href="https://pnpm.io/pnpm-workspace_yaml" rel="noopener noreferrer"&gt;pnpm-workspace.yaml&lt;/a&gt; first. If you don't, then at best &lt;code&gt;pnpm import&lt;/code&gt; will only import the dependencies declared in your root file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependencies which have undeclared dependencies
&lt;/h3&gt;

&lt;p&gt;Another kind of problem we ran into is some dependencies having undeclared dependencies. When using &lt;em&gt;yarn&lt;/em&gt; it was not a problem because those undeclared dependencies are sometimes very used. For example, after the migration we realized &lt;code&gt;mdjs-core&lt;/code&gt; &lt;a href="https://github.com/modernweb-dev/rocket/pull/278" rel="noopener noreferrer"&gt;had not declared its dependency on &lt;code&gt;slash&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A simple way to fix this is again through the &lt;a href="https://pnpm.io/pnpmfile#hooksreadpackagepkg-context-pkg--promisepkg" rel="noopener noreferrer"&gt;readPackage hook&lt;/a&gt; we mentioned in the previous section. There, you can simply declare the dependency explicitly for &lt;code&gt;mdjs-core&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mdjs/core&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;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependencies&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;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;^3.0.0&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;h3&gt;
  
  
  shamefully-hoist when tools don't play along
&lt;/h3&gt;

&lt;p&gt;We talked about the non-flat node-modules earlier. This structure is unfortunately not compatible with every Node tool.&lt;/p&gt;

&lt;p&gt;An example of this is Astro which &lt;a href="https://github.com/withastro/astro/blob/74372a7d269882fe8bbcbf02a85a79296d58668f/examples/minimal/.npmrc#L2" rel="noopener noreferrer"&gt;at the moment recommends using &lt;code&gt;shamefully-hoist&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
Kind of a funny name, meant to dissuade you from using it :-)&lt;/p&gt;

&lt;p&gt;As the name implies, this one will hoist all your dependencies in your root node_modules, fixing any incompatibility you may have with dev tools not playing along with the nested node_modules. This typically happens because they don't resolve symlinks to their target.&lt;/p&gt;

&lt;p&gt;At the time of this writing, Astro requiring it, if you're not using it will fail at loading its dependencies, with a&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error: The following dependencies are imported but could not be resolved:

  react &lt;span class="o"&gt;(&lt;/span&gt;imported by /not-relevant/testimonial-card/src/index.tsx&lt;span class="o"&gt;)&lt;/span&gt;
  svelte/internal &lt;span class="o"&gt;(&lt;/span&gt;imported by /not-relevant/double-cta/dist/DoubleCta.svelte.js&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of going this way, I preferred adding manually the missing dependencies to the workspace using Astro. It's a hack, but one I prefer living with than using &lt;code&gt;shamefully-hoist&lt;/code&gt; globally as it would cancel out the advantages of the non-flat node-modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  How fast is it
&lt;/h3&gt;

&lt;p&gt;I know, that was the whole point of us trying out pnpm - let's see how fast it is!&lt;/p&gt;

&lt;p&gt;So, when the cache is hit, we get down to 24 seconds:&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%2F2rxad0aml5srrr0b7wuk.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%2F2rxad0aml5srrr0b7wuk.jpg" alt="pnpm install with cache taking 24 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when the cache cannot be used, we get down to a whopping 53 seconds:&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%2F2xz7kp490dp1vffyxa8w.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%2F2xz7kp490dp1vffyxa8w.jpg" alt="pnpm install without cache taking 53 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Summarizing the results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Without cache&lt;/th&gt;
&lt;th&gt;With cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;yarn 2 (without dedupe)&lt;/td&gt;
&lt;td&gt;6min 31s&lt;/td&gt;
&lt;td&gt;1min 11s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yarn 3 (without dedupe)&lt;/td&gt;
&lt;td&gt;4min 50s&lt;/td&gt;
&lt;td&gt;57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yarn 3&lt;/td&gt;
&lt;td&gt;4min 1s&lt;/td&gt;
&lt;td&gt;50s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yarn 3 (optimized)&lt;/td&gt;
&lt;td&gt;1min 10&lt;/td&gt;
&lt;td&gt;45s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pnpm&lt;/td&gt;
&lt;td&gt;58s&lt;/td&gt;
&lt;td&gt;24s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honestly, I'm particularly impressed about the results when there's no cache.&lt;br&gt;
I would have expected network to be the bottleneck for both yarn or pnpm in that case, but somehow pnpm still really shines there, while also being faster (at least for us) when the cache is used too!&lt;/p&gt;

&lt;p&gt;Now I'm happy - the CI is snappy, at least way snappier than it was, and our local install times also benefitted from it. Thank you pnpm!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tooling</category>
      <category>npm</category>
    </item>
    <item>
      <title>Vite in the browser</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 19 Jan 2022 17:39:06 +0000</pubDate>
      <link>https://forem.com/divriots/vite-in-the-browser-354p</link>
      <guid>https://forem.com/divriots/vite-in-the-browser-354p</guid>
      <description>&lt;p&gt;
  TL;DR
  &lt;p&gt;We made &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; - a patched version of &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; running in the browser with Workers.&lt;/p&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works - in a nutshell
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"&gt;Service Worker&lt;/a&gt;: replaces Vite's HTTP server. Capturing the HTTP calls of an embedded iframe from example.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"&gt;Web Worker&lt;/a&gt;: Run &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; to process off the main thread.&lt;/li&gt;
&lt;li&gt;Calls to the file system are replaced by an in-memory file system.&lt;/li&gt;
&lt;li&gt;Import of files with special extensions (&lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt;, &lt;code&gt;.scss&lt;/code&gt;...) are transformed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No real File System
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; does a lot with files. The files of the project but also config files, watchers, and globs. These are difficult to implement in the browser with a shimmed in-memory FS. We removed watchers, globs, and config file calls to limit the complexity and the surface API.&lt;/p&gt;

&lt;p&gt;The project files stay in the in-memory FS that &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; and vite plugins can access normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  No "node_modules"
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; relies on the presence of &lt;code&gt;node_modules&lt;/code&gt; to resolve dependencies. And it bundles them in a &lt;a href="https://vitejs.dev/guide/dep-pre-bundling.html"&gt;Dependencing Pre-Bundling&lt;/a&gt; optimization at startup.&lt;/p&gt;

&lt;p&gt;We didn't want to run a &lt;code&gt;node_modules&lt;/code&gt; folder in the browser's memory because we think it's just too much data to download and store into the browser's memory. So we carefully stripped out node resolvers and &lt;a href="https://vitejs.dev/guide/dep-pre-bundling.html"&gt;Dependencing Pre-Bundling&lt;/a&gt; from &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Users of &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; have to create a &lt;a href="https://vitejs.dev/guide/api-plugin.html"&gt;Vite plugin&lt;/a&gt; to resolve bare module imports.&lt;/p&gt;

&lt;p&gt;Our products: &lt;a href="https://backlight.dev"&gt;Backlight.dev&lt;/a&gt;, &lt;a href="https://components.studio"&gt;Components.studio&lt;/a&gt; and &lt;a href="https://webcomponents.dev"&gt;WebComponents.dev&lt;/a&gt;, are running a server-side bundler optimizer for the past 2 years now. We created a Vite plugin for &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; to resolve node dependencies automatically. As of the date of this post, this server-side bundler is not open-sourced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regex "lookbehind"
&lt;/h3&gt;

&lt;p&gt;Some regexs in &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; are using &lt;a href="https://www.regular-expressions.info/lookaround.html"&gt;lookbehind&lt;/a&gt;. This works great locally when executed by &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt;, but &lt;a href="https://caniuse.com/js-regexp-lookbehind"&gt;it's not supported in Safari&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So we rewrote the regexs for more browser compatibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Module Reload (HMR)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSockets&lt;/a&gt; to communicate code changes from the server (node) to the client (browser).&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;, the server is the ServiceWorker + Vite worker and the client is the iframe. So we changed the communication from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSockets&lt;/a&gt; to a post message to the iframe.&lt;/p&gt;

&lt;p&gt;For this, the client side code of Vite in iframe has been replaced by &lt;a href="https://github.com/divriots/browser-vite/blob/browser-vite/packages/vite/src/client/browser.ts"&gt;a special browser version&lt;/a&gt; handling messages outside of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSockets&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;As of the time of this writing, it's not a plug and play process. There is a lot to figure out by reading Vite's internal processing in order to use &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note: This post may become obsolete over time, so make sure you check&lt;br&gt;
&lt;a href="https://github.com/divriots/browser-vite/blob/browser-vite/README.md#usage"&gt;browser-vite's README&lt;/a&gt; for always up to date information on browser-vite's usage.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Install the &lt;a href="https://npmjs.org/browser-vite"&gt;browser-vite npm package&lt;/a&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="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; browser-vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&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="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; vite@npm:browser-vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To channel "vite" imports to "browser-vite".&lt;/p&gt;

&lt;h3&gt;
  
  
  iframe - window to browser-vite
&lt;/h3&gt;

&lt;p&gt;You need an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"&gt;iframe&lt;/a&gt; that will show the pages served internally by &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Worker - the in-browser web server
&lt;/h3&gt;

&lt;p&gt;The Service Worker will capture certain URLs requests coming from the iframe.&lt;/p&gt;

&lt;p&gt;Here is an example using &lt;a href="https://developers.google.com/web/tools/workbox"&gt;workbox&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="sr"&gt;/^https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="sr"&gt;HOST/&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&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="nx"&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;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;request&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;url&lt;/span&gt;&lt;span class="p"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workbox-routing/types/RouteHandler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;RouteHandlerCallbackContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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;pathname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="c1"&gt;// send the request to vite worker&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;postToViteWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;response&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;Mostly posting a message to the "Vite Worker" using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage"&gt;postMessage&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API"&gt;broadcast-channel&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vite Worker - processing request
&lt;/h3&gt;

&lt;p&gt;The Vite Worker is a Web Worker that will process requests captured by the Service Worker.&lt;/p&gt;

&lt;p&gt;Example of creating a Vite Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;transformWithEsbuild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModuleGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createPluginContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createDevHtmlTransformFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;resolveConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;generateCodeFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ssrTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ssrLoadModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ViteDevServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PluginOption&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;browser-vite&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createServer&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resolveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// virtual plugin to provide vite client/env special entries (see below)&lt;/span&gt;
        &lt;span class="nx"&gt;viteClientPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// virtual plugin to resolve NPM dependencies, e.g. using unpkg, skypack or another provider (browser-vite only handles project files)&lt;/span&gt;
        &lt;span class="nx"&gt;nodeResolvePlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// add vite plugins you need here (e.g. vue, react, astro ...)&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// as hooked in service worker&lt;/span&gt;
      &lt;span class="c1"&gt;// not really used, but needs to be defined to enable dep optimizations&lt;/span&gt;
      &lt;span class="na"&gt;cacheDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VFS_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// any other configuration (e.g. resolve alias)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serve&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;plugins&lt;/span&gt; &lt;span class="o"&gt;=&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;plugins&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;pluginContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createPluginContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&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;moduleGraph&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;ModuleGraph&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;url&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;pluginContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolveId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;watcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;what&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&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;watcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;add&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;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViteDevServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&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;pluginContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;moduleGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;transformWithEsbuild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&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="nx"&gt;ssrTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;printUrls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;_globImporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;send&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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// send HMR data to vite client in iframe however you want (post/broadcast-channel ...)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="nx"&gt;off&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;watcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;ssrLoadModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;ssrLoadModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loadModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;ssrFixStacktrace&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="nx"&gt;close&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="nx"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;_optimizeDepsMetadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_isRunningOptimizer&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;_ssrExternals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;_restartPromise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_forceOptimizeOnRestart&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;_pendingRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&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;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformIndexHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createDevHtmlTransformFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// apply server configuration hooks from plugins&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postHooks&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="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;void&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;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;plugin&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;plugins&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;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;postHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&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="c1"&gt;// run post config hooks&lt;/span&gt;
  &lt;span class="c1"&gt;// This is applied before the html middleware so that user middleware can&lt;/span&gt;
  &lt;span class="c1"&gt;// serve custom content instead of index.html.&lt;/span&gt;
  &lt;span class="nx"&gt;postHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;fn&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;fn&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pluginContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buildStart&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runOptimize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&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;server&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;Pseudo code to process requests via &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;transformRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isCSSRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isDirectCSSRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;injectQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;removeImportQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unwrapId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handleFileAddUnlink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handleHMRUpdate&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;vite/dist/browser&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="nx"&gt;req&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;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accept&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// strip ?import&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;removeImportQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Strip valid id prefix. This is prepended to resolved Ids that are&lt;/span&gt;
  &lt;span class="c1"&gt;// not valid browser import specifiers by the importAnalysis plugin.&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unwrapId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// for CSS, we need to differentiate between normal CSS requests and&lt;/span&gt;
  &lt;span class="c1"&gt;// imports&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;isCSSRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/css&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;injectQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;direct&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;let&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&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;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformIndexHtml&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;path&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&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;else&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;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Return code reponse&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Return error response&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;Check &lt;a href="https://github.com/vitejs/vite/tree/main/packages/vite/src/node/server/middlewares"&gt;Vite's internal middlewares&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it compare to Stackblitz WebContainers
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"&lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt;:&lt;br&gt;
Run Node.js natively in your browser"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/"&gt;Stackblitz&lt;/a&gt;'s &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; can also run &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; in the browser. You can elegantly go to &lt;a href="https://vite.new"&gt;vite.new&lt;/a&gt; to have a working environment.&lt;/p&gt;

&lt;p&gt;We are not experts in WebContainers but, in a nutshell, where &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; shims the FS and the HTTPS server at the Vite level, WebContainers shims the FS and a lot of other things at the &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt; level, and Vite runs on it with a few additional changes.&lt;/p&gt;

&lt;p&gt;It goes as far as storing a &lt;code&gt;node_modules&lt;/code&gt; in the WebContainer, in the browser. But it doesn't run &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; directly because it would take too much space (I guess). They aliased these commands to &lt;a href="https://developer.stackblitz.com/docs/platform/turbo"&gt;Turbo&lt;/a&gt; - their package manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; can run other frameworks too, like &lt;a href="https://blog.stackblitz.com/posts/remix-runs-on-webcontainers/"&gt;Remix&lt;/a&gt;, &lt;a href="https://blog.stackblitz.com/posts/sveltekit-supported-in-webcontainers/"&gt;SvelteKit&lt;/a&gt; or &lt;a href="https://blog.stackblitz.com/posts/astro-support/"&gt;Astro&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's magical ✨ It's mind-blowing 🤯 We have massive respect for what the Stackblitz team has built here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One downside of &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; is that it can &lt;a href="https://developer.stackblitz.com/docs/platform/browser-support"&gt;only run on Chrome today&lt;/a&gt; but will probably &lt;a href="https://developer.stackblitz.com/docs/platform/browser-support#testing-on-firefox"&gt;run on Firefox soon&lt;/a&gt;. &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; works on Chrome, Firefox and Safari today.&lt;/p&gt;

&lt;p&gt;In a nutshell, &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; operates at a lower level of abstraction to run &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; in the browser. &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; operates at a higher level of abstraction, very close to &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; itself.&lt;/p&gt;

&lt;p&gt;Metaphorically, for the retro-gamers out there, &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; is a little bit like &lt;a href="https://en.wikipedia.org/wiki/UltraHLE"&gt;UltraHLE&lt;/a&gt; 🕹️😊&lt;/p&gt;

&lt;p&gt;(*) &lt;a href="https://emulation.gametechwiki.com/index.php/High/Low_level_emulation"&gt;gametechwiki.com: High/Low level emulation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; is at the heart of our solutions. We are progressively rolling it out to all our products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://backlight.dev"&gt;Backlight.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://components.studio"&gt;Components.studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webcomponents.dev"&gt;WebComponents.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://replic.dev"&gt;Replic.dev&lt;/a&gt; (New app coming up very soon!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Going forward, we will continue to invest in &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; and report back upstream. Last month, we also announced that &lt;a href="https://dev.to/blog/supporting-vitejs"&gt;we sponsored Vite via Evan You and Patak&lt;/a&gt; to support this wonderful project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to know more?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Repository: &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Join our &lt;a href="https://discord.gg/XkQxSU9"&gt;Discord server&lt;/a&gt;, we have a #browser-vite channel going on 🤗&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>vite</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How ESLint Can Enforce Your Design System Best Practices</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 19 Jan 2022 17:34:31 +0000</pubDate>
      <link>https://forem.com/divriots/how-eslint-can-enforce-your-design-system-best-practices-37ad</link>
      <guid>https://forem.com/divriots/how-eslint-can-enforce-your-design-system-best-practices-37ad</guid>
      <description>&lt;p&gt;If you're creating a design system component library for your company or the open-source community, there's a good chance that you have strong opinions on how end-users should consume your design system.&lt;/p&gt;

&lt;p&gt;To ensure that your design system is used in its intended way, and to reduce the number of possible bugs, you might want your users to adhere to your best practices. The following are two examples of possible best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoiding inline styles in your elements&lt;/li&gt;
&lt;li&gt;Ensuring that tooltips don't contain interactive content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're the only person designing, developing, and consuming your design system, then you can sleep comfortably knowing that your design system is being used exactly as intended.&lt;/p&gt;

&lt;p&gt;Chances are you're not the only person developing the design system and you'll certainly not be present when someone consumes it. How can you feel sure that everyone abides by your design system's best practices? You could cross your fingers and trust that your end-users read the documentation, heed your warnings, and never fail to abide by your rules.&lt;/p&gt;

&lt;p&gt;Unfortunately, this is often not the case and it's very easy to miss warnings or misunderstand how to properly use a tool. I've been there!&lt;/p&gt;

&lt;p&gt;Fortunately, a great way to encourage your consumers to follow your best practices is through the use of &lt;a href="https://eslint.org/"&gt;ESLint&lt;/a&gt;, a static analysis tool to find problems in your code.&lt;/p&gt;

&lt;p&gt;By default, ESLint ships with a handful of general best practices, called &lt;em&gt;rules&lt;/em&gt; and will display red squigglys in your IDE if the rules have been violated. Some of these rules include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No duplicate keys in objects&lt;/li&gt;
&lt;li&gt;No unreachable code&lt;/li&gt;
&lt;li&gt;No unused variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the rules you enable in your project don't need to come directly from ESLint. Popular libraries like &lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;, &lt;a href="https://lodash.com/"&gt;Lodash&lt;/a&gt;, and &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; have ESLint configurations that anyone can use in their own projects to ensure users adhere to best practices. If you're an intrepid explorer of the JavaScript language you can go a step further and create custom rules specific to your design system which you can export for other people to use in their projects. That's exactly what we'll do in these articles.&lt;/p&gt;




&lt;p&gt;In this article, we'll spend a bit of time understanding how tools like ESLint parse JavaScript down into a data structure called an &lt;em&gt;abstract syntax tree&lt;/em&gt; (AST). We'll then touch on how ESLint rules work and how to parse our Lit templates into HTML. Finally we'll start creating our rules. We'll even use ESLint's built-in testing tool to make sure our rules work under a variety of conditions.&lt;/p&gt;

&lt;p&gt;The pre-requisite for this article is some JavaScript + HTML knowledge. A little experience using ESLint and Lit may come in handy but isn't necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s an Abstract Syntax Tree?
&lt;/h2&gt;

&lt;p&gt;For those, like me, who haven't gotten their hands dirty with compilers before, conceptualising how the human-readable language we write in our IDE gets understood (and transformed) by tools like &lt;a href="https://webpack.js.org/"&gt;Webpack&lt;/a&gt;, &lt;a href="https://prettier.io/"&gt;Prettier&lt;/a&gt;, and &lt;a href="https://babeljs.io/"&gt;Babel&lt;/a&gt; can feel like magic.&lt;/p&gt;

&lt;p&gt;Under the hood, when a tool like ESLint wants to start performing actions against your JavaScript it &lt;em&gt;parses&lt;/em&gt; your code. Parsing is the process of taking the JavaScript you've written and turning it into a tree representation of the code, an &lt;em&gt;abstract syntax tree&lt;/em&gt; (AST).&lt;/p&gt;

&lt;p&gt;This process of parsing is split into two parts, &lt;em&gt;tokenization&lt;/em&gt; and &lt;em&gt;tree construction&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Tokenization takes the code and splits it into things called tokens which describe isolated parts of the syntax.&lt;/p&gt;

&lt;p&gt;Tokens for a JavaScript program like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&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;will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&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;IdentifierName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;const&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;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;WhiteSpace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&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="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;IdentifierName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;helloWorld&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;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;WhiteSpace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&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="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;Punctuator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&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="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;WhiteSpace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&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="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;StringLiteral&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'hello world'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;closed&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="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;
  JS Tokens
  &lt;p&gt;I used &lt;a href="https://github.com/lydell/js-tokens"&gt;js-tokens&lt;/a&gt; as a quick way of tokenizing my JS for this example but we won't be dealing with tokenization directly ourselves in this article.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;The second step in the parsing process is &lt;em&gt;tree construction&lt;/em&gt;, which reformats the tokens into an AST. The AST describes each part of the syntax and its relationship to the others.&lt;/p&gt;

&lt;p&gt;We can visualise this relationship by parsing the following JavaScript statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Creating custom ESLint rules&amp;lt;/h1&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;It would get transformed into an AST, with the following structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/blog/best-practices-w-eslint/parser.drawio.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/blog/best-practices-w-eslint/parser.drawio.png" alt="AST representation of the previous code sample"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tools like Babel and Prettier turn your written JavaScript into an AST to analyse and transform the code we've written. Babel uses the AST to transpile our code into a browser-friendly version of JavaScript, while Prettier uses the AST to reformat your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Curious with the AST Explorer
&lt;/h2&gt;

&lt;p&gt;To really explore what an AST looks like, have a play with the &lt;a href="https://astexplorer.net/"&gt;AST explorer&lt;/a&gt;. Get familiar with the AST explorer as we're gonna be using it loads later in the article.&lt;/p&gt;

&lt;p&gt;Write a simple statement, like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&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;You'll see that the top level of the tree describes the entire program and we can look into the &lt;em&gt;body&lt;/em&gt; array to see the individual constituents of our above statement represented in the AST.&lt;/p&gt;

&lt;p&gt;If you hover over the &lt;code&gt;VariableDeclaration&lt;/code&gt; you can see that the entire statement on the left gets highlighted. If we go a level deeper into the &lt;code&gt;declarations&lt;/code&gt; array you'll see an additional node &lt;code&gt;VariableDeclarator&lt;/code&gt;. If we keep going we'll eventually reach rock bottom. In the case of our hello world statement, it's with the variable's &lt;code&gt;Identifier&lt;/code&gt; and the variable's &lt;code&gt;Literal&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Let's revisit our component from earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Creating custom ESLint rules&amp;lt;/h1&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;If you step through the tree in the AST explorer you can see that the structure matches our image from earlier. Pay particular attention to the &lt;code&gt;TaggedTemplateExpression&lt;/code&gt; node and the &lt;code&gt;TemplateLiteral&lt;/code&gt; node. These are the ones that will come in handy when we write our ESLint rules.&lt;/p&gt;

&lt;p&gt;Our call to the &lt;code&gt;html&lt;/code&gt; function is an expression, but it looks a little different from other function definitions. Let's see how the AST differs with an expression like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;heyThere&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;hey&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;heyThere&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we hover over the &lt;code&gt;heyThere()&lt;/code&gt; &lt;code&gt;ExpressionStatement&lt;/code&gt;, we see that the properties match our &lt;code&gt;html&lt;/code&gt; ExpressionStatement. The main difference is that the value in the &lt;code&gt;expression&lt;/code&gt; property looks different. The expression this time is a &lt;code&gt;CallExpression&lt;/code&gt;, which has a set of properties different to that of our &lt;code&gt;TaggedTemplateExpression&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we look back at our &lt;code&gt;TaggedTemplateExpression&lt;/code&gt;, we can see that we have properties like tag and quasi.&lt;/p&gt;

&lt;p&gt;The tag gives us some details about the function name. Which in this case is &lt;code&gt;html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means when writing our ESlint rule we'll be able to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Some ESLint psuedo-code&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createRule&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;TaggedTemplateExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;isLitExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&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;isLitExpression&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 rule&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// do nothing&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;Finally, if you look into the &lt;code&gt;TaggedTemplateExpression&lt;/code&gt; object, you'll see a property named &lt;code&gt;quasi&lt;/code&gt;. This property contains our two noteworthy properties &lt;code&gt;expressions&lt;/code&gt; and &lt;code&gt;quasis&lt;/code&gt;. Take the following expression:&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/blog/best-practices-w-eslint/taggedtemplateexpression.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/blog/best-practices-w-eslint/taggedtemplateexpression.png" alt="Tagged Template Expression Highlights"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blue underlines, the first and third respectively, will live in the &lt;code&gt;quasis&lt;/code&gt; array and they'll be in the order that they're written in your template literal.&lt;/p&gt;

&lt;p&gt;The green underline, the second one, will live in the &lt;code&gt;expressions&lt;/code&gt; array, and provides a reference to the name of the variable. Like the &lt;code&gt;quasis&lt;/code&gt;, the items in the array are in the order that they're defined. This makes it very easy to reconcile your template literal later on.&lt;/p&gt;

&lt;p&gt;
  Quasis
  &lt;p&gt;When accessing the value in your quasis, you'll see the string available as either &lt;em&gt;raw&lt;/em&gt; or &lt;em&gt;cooked&lt;/em&gt;. These values determine whether escape sequences are ignored or interpreted. Axel Rauschmayer covers this in a little more detail in &lt;a href="https://2ality.com/2016/09/template-literal-revision.html"&gt;this article&lt;/a&gt;.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;Here's a question for you, what happens if the first character of our template literal is an expression? How is this represented in our AST? Try the following snippet in the AST explorer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, how you doin'?`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take a little more time exploring quasis and expressions if they still feel unfamiliar to you.&lt;/p&gt;

&lt;p&gt;Fortunately, we won't need to directly deal with the parsing process when writing our ESLint rules. We've covered a lot of ground because having a high-level understanding of how the tooling works, makes for a much more intuitive development experience later on.&lt;/p&gt;

&lt;p&gt;
  Super Tiny Compiler
  &lt;p&gt;If you're interested in learning a little more about the whole compilation process, the &lt;a href="https://github.com/jamiebuilds/the-super-tiny-compiler/blob/master/the-super-tiny-compiler.js"&gt;Super Tiny Compiler&lt;/a&gt; is a really fun way of building your own JavaScript compiler using only a couple of hundred lines of code.&lt;/p&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do Eslint Rules Work?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Visitor Pattern
&lt;/h3&gt;

&lt;p&gt;Fortunately, we don't need to make any transformation when writing ESLint rules and instead we write our checks against specific node types in our code. These nodes are slices from our code's AST.&lt;/p&gt;

&lt;p&gt;Once ESLint has parsed your code into an AST, it then traverses your tree, &lt;em&gt;visiting&lt;/em&gt; each node along the way. For those familiar with programming design patterns, you might recognise this pattern as the &lt;em&gt;visitor&lt;/em&gt; pattern.&lt;/p&gt;

&lt;p&gt;The visitor pattern is a way of running some new logic against an object without modifying the object. ESLint uses the visitor pattern to separate the code used to run checks against your code from the AST.&lt;/p&gt;

&lt;p&gt;Let's take a look at the &lt;a href="https://codesandbox.io/s/mystifying-morning-lk3xh?file=/src/index.js"&gt;visitor pattern in action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see, that I've implemented the visitor using 3 blocks of code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ast.js&lt;/code&gt;: The AST for &lt;code&gt;const name = 'andrico'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;traverser.js&lt;/code&gt;: An algorithm that traverses the nodes of our AST.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;visitors.js&lt;/code&gt;: An object of methods where a given method fires once the traverser reaches its corresponding node. In our case, when the traverser reaches a &lt;code&gt;VariableDeclarator&lt;/code&gt; node, it fires off our visitor function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's break down the &lt;code&gt;traverser&lt;/code&gt; a little more:&lt;/p&gt;

&lt;p&gt;We begin in &lt;code&gt;index.js&lt;/code&gt; by creating an instance of our &lt;code&gt;Traverser&lt;/code&gt; class and passing through our AST and our visitors. Under the hood, our &lt;code&gt;Traverser&lt;/code&gt; class stores our AST and visitors as instance variables for us to use later.&lt;/p&gt;

&lt;p&gt;We then invoke the instance's &lt;code&gt;traverse&lt;/code&gt; method. If you move to the &lt;code&gt;traverser.js&lt;/code&gt; file, you can see that when we invoke &lt;code&gt;traverse&lt;/code&gt; 5 things can happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The node is &lt;code&gt;null&lt;/code&gt;, which will happen when we manually invoke the &lt;code&gt;traverse&lt;/code&gt; method without any arguments. When this happens, we kick off the traversal function using the AST we stored during the class's initialisation.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;node&lt;/em&gt; has a type of &lt;code&gt;Program&lt;/code&gt;, which will happen for the top-level nodes in our AST. When this happens we recursively call the traversal method on the child nodes.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;node&lt;/em&gt; has a type that matches a visitor function. When this happens, we fire our visitor function and pass through the node as an argument.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;node&lt;/em&gt; has additional declarations, so we continue calling our traversal function on those child declarations.&lt;/li&gt;
&lt;li&gt;Our &lt;em&gt;node&lt;/em&gt; satisfies none of these conditions, which will cause our traversal method to exit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the context of our &lt;code&gt;const name = 'andrico'&lt;/code&gt; example, our traversal function will continue making its way through the AST until it reaches the &lt;code&gt;VariableDeclarator&lt;/code&gt;, where it will invoke the visitor we defined in &lt;code&gt;visitors.js&lt;/code&gt;. In this visitor we check to see if the value is &lt;code&gt;Andrico&lt;/code&gt; and if it is, we log a message saying that it's an invalid name (though I kinda like it).&lt;/p&gt;

&lt;p&gt;Open the console in the CodeSandbox and see what it outputs. Try changing the check in your visitor and see what happens, if anything.&lt;/p&gt;

&lt;p&gt;The good news is that ESLint handles the traversal logic for our JavaScript. The other good news is that we'll need to implement the traversal logic for our parsed HTML. 😄&lt;/p&gt;

&lt;p&gt;
  Open WC's eslint-plugin-lit-a11y
  &lt;p&gt;This section was heavily informed by my recent involvement with Open WC's &lt;a href="https://github.com/open-wc/open-wc/tree/master/packages/eslint-plugin-lit-a11y"&gt;eslint-plugin-lit-a11y&lt;/a&gt; and &lt;a href="https://github.com/43081j/eslint-plugin-lit"&gt;eslint-plugin-lint&lt;/a&gt;. If you'd like to learn more about (or try a handful of) ESLint rules focused on web components, these are your go-to repos.&lt;/p&gt;



&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does an Eslint Rule Look Like?
&lt;/h3&gt;

&lt;p&gt;Writing an ESLint rule doesn't require anything fancy, it's just a plain ol' JavaScript object. The object's top-level can receive two properties: &lt;code&gt;meta&lt;/code&gt; and &lt;code&gt;create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;meta&lt;/code&gt; provides the metadata for the rule.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;create&lt;/code&gt; property is a function that returns an object of visitors that ESLint calls when it visits each node. This follows the same principle as the snippets in the codesandbox. And much like the demo in our codesandbox, the name of each visitor function is the name of the node that we want to visit.&lt;/p&gt;

&lt;p&gt;In fact, we can even repurpose the pseudo-code from earlier, and decorate it with the ESLint specific boilerplate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;create&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;create&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;TaggedTemplateExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;isLitExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&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;isLitExpression&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 rule&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// do nothing&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;The &lt;code&gt;create&lt;/code&gt; function also provides a context object, which provides some additional helpers and information about the current rule. The helper we're most concerned with right now is the &lt;code&gt;report()&lt;/code&gt; method. We can call &lt;code&gt;report&lt;/code&gt; whenever we want an ESLint error to display in the console or the IDE.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://Context.report"&gt;Context.report&lt;/a&gt; takes an object with a handful of properties, but we're most interested in the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;message: the description of the problem&lt;/li&gt;
&lt;li&gt;node: the AST node related to the problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Additional information
  &lt;p&gt;We can pass through additional information, like the line of code we want to report the error on, but that's outside the scope of this tutorial.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;Before continuing, why not think about adjusting the pseudocode above to display an ESLint error when a tagged template is invoked, and the template literal has no content, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a basic understanding of JavaScript's AST, the visitor pattern, and the anatomy of an ESLint rule, the only thing left to cover is how to parse our template string into HTML before we can start creating our rules.&lt;/p&gt;

&lt;p&gt;For a more in-depth read into the anatomy of an ESLint rule, there's no better place to look than the &lt;a href="https://eslint.org/docs/developer-guide/working-with-rules"&gt;official docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Can We Transform Our Templates into HTML?
&lt;/h2&gt;

&lt;p&gt;When using ESLint, we have the luxury of ESLint providing us with our parsed JavaScript AST. And while ESLint can't parse our HTML, we can use a library like &lt;code&gt;[parse5](https://github.com/inikulin/parse5)&lt;/code&gt; to parse a valid HTML string into a data structure, not unlike our JavaScript AST.&lt;/p&gt;

&lt;p&gt;The AST explorer we've spent so much time exploring even has settings for displaying &lt;a href="https://astexplorer.net/#/1CHlCXc4n4"&gt;HTML ASTs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since one of our rules is going to prevent us from passing through inline styles, let's see how the following gets represented as an AST:&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;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:inline;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Main content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we dive into the AST and look for our div, we can see we're presented with some useful information. The most notable are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tagName&lt;/strong&gt;: Which is the name of the html element. (in this case &lt;code&gt;div&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;attrs&lt;/strong&gt;: Which is an array of attributes, represented as a key-value pair. Our div's &lt;code&gt;attrs&lt;/code&gt; property holds a single item. The item has a &lt;code&gt;name&lt;/code&gt; of &lt;code&gt;style&lt;/code&gt; and a &lt;code&gt;value&lt;/code&gt; of &lt;code&gt;display:inline;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using this information we can already start seeing how to piece together everything we've learned to create our first lint rule.&lt;/p&gt;

&lt;p&gt;Here's how we can parse our JavaScript templates using the &lt;code&gt;parse5&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;parse5&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;parse5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// We're defining out HTML templates&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div style="display:inline;"&amp;gt;Main content&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// We're passing through an HTML snippet to parseFragment, which returns our HTML AST&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedFragment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parse5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseFragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// We access the first child because the top-level contains metadata we don't need right now.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedFragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;childNodes&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="c1"&gt;// We check to see if there are any style attributes in our div&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasStyleAttr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;attr&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;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If there are, we report an error&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;hasStyleAttr&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL&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;Thanks to tools like parse 5 and ESLint, we can offload a lot of the complex processing, and focus on writing the code for our specific rules. This is what we'll start doing from the next article onwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe Check
&lt;/h2&gt;

&lt;p&gt;
  Further Reading
  &lt;ul&gt;
&lt;li&gt;&lt;a href="https://babeljs.io/docs/en/babel-parser"&gt;Babel Parser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lydell/js-tokens"&gt;js-tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://astexplorer.net/"&gt;AST Explorer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://egghead.io/lessons/javascript-introduction-to-abstract-syntax-trees"&gt;Abstract Syntax Trees Kent Dodds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.sessionstack.com/how-javascript-works-parsing-abstract-syntax-trees-asts-5-tips-on-how-to-minimize-parse-time-abfcf7e8a0c8"&gt;Parsing and ASTs in JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jamiebuilds/the-super-tiny-compiler/blob/master/the-super-tiny-compiler.js"&gt;Super Tiny Compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch1.md"&gt;You Don't Know JS Yet&lt;/a&gt; - Chapter 1&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint.org/docs/developer-guide/working-with-rules"&gt;Working with rules - ESLint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint.org/docs/developer-guide/nodejs-api#ruletester"&gt;ESLint RuleTester&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#interactive_content"&gt;Interactive content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md"&gt;Babel plugin handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-wc/open-wc/tree/master/packages/eslint-plugin-lit-a11y"&gt;ESLint Plugin Lit A11y&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/43081j/eslint-plugin-lit"&gt;ESLint Plugin Lit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;p&gt;We've covered a lot of theory so far, and a lot of separate ideas. We'll bring everything together in the next article.&lt;/p&gt;

&lt;p&gt;Let's have a vibe check, if something doesn't quite make sense at this point, it's worth giving it a quick re-review. And if things still aren't clear, that's probably on me, so feel free to &lt;a href="https://twitter.com/andricokaroulla?lang=en"&gt;reach out and let me know how I can make things even clearer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before we move on, let's go through the key points one last time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The JavaScript we write gets parsed into an AST, which tools can use to validate or transform our code&lt;/li&gt;
&lt;li&gt;Each part of our JavaScript code is represented as a node, as seen in the AST explorer.&lt;/li&gt;
&lt;li&gt;ESLint then traverses our AST, and invokes our visitor functions whenever it &lt;em&gt;visits&lt;/em&gt; a node we're interested in.&lt;/li&gt;
&lt;li&gt;Once ESLint invokes our visitor function, we can start running checks against the node.&lt;/li&gt;
&lt;li&gt;We can then check to see if the node that gets passed to our function is a &lt;code&gt;lit&lt;/code&gt; &lt;em&gt;TaggedTemplateExpression&lt;/em&gt;, and if it is, we can grab its HTML fragment, which we can build by consolidating the expressions and quasis.&lt;/li&gt;
&lt;li&gt;We'll use &lt;code&gt;parse5&lt;/code&gt; to parse the fragment and give us our HTML's AST.&lt;/li&gt;
&lt;li&gt;We now have everything we need to run our checks, like seeing if a certain attribute is present when it shouldn't be.&lt;/li&gt;
&lt;li&gt;We can then invoke ESLint's report function if the rule has been violated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We've learned a lot of theory, and the best thing to do with all that theory is to put it into practice. In the next two articles, we're going to be creating a couple of ESLint rules and taking everything we've learned into something you can use for your own design systems.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Design systems supercharged </title>
      <dc:creator>RBerthier</dc:creator>
      <pubDate>Tue, 07 Dec 2021 15:39:31 +0000</pubDate>
      <link>https://forem.com/divriots/design-systems-supercharged-2p92</link>
      <guid>https://forem.com/divriots/design-systems-supercharged-2p92</guid>
      <description>&lt;p&gt;Building great front-end products is hard and time-consuming. &lt;/p&gt;

&lt;p&gt;Web technologies are evolving at high pace, designers and developers struggle to collaborate on handover, building consistent web components requires loads of works, documentation is too often not up-to-date, maintaining apps over time is an Herculean task, ... &lt;/p&gt;

&lt;p&gt;You know the story.&lt;/p&gt;

&lt;p&gt;Implementing a Design System is a good solution to tackle these challenges. The Design System is the source of truth for all reusable tokens and components to be consumed in web apps. Building and managing a Design System, as a part of a production pipeline, will greatly improve team efficiency and product consistency. &lt;/p&gt;

&lt;p&gt;A good Design System should help teams collaborate, improve reusability, manage technologies evolution, build documentation, ship faster / better, and lower maintenance. But building and maintaining the tooling to manage a Design System remains quite time consuming too ... &lt;/p&gt;

&lt;p&gt;That's why we launched &lt;a href="https://backlight.dev/"&gt;Backlight.dev&lt;/a&gt;, a powerful and collaborative platform to build Design Systems on the code side. Backlight helps front-end teams to deliver value by focusing on their real work, not on maintaining tools. &lt;/p&gt;

&lt;p&gt;Backlight comes with some nice features : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All-in-one : tokens, components, code, stories, tests, documentation, designs, ...&lt;/li&gt;
&lt;li&gt;Technologies / frameworks agnostic&lt;/li&gt;
&lt;li&gt;Real-time preview rendering for tokens, components and documentation&lt;/li&gt;
&lt;li&gt;Real-time and asynchronous collaboration&lt;/li&gt;
&lt;li&gt;Live documentation (MDX with embedded components and code snippets)&lt;/li&gt;
&lt;li&gt;Versioning and repo management with Github / Gitlab / Bitbucket&lt;/li&gt;
&lt;li&gt;Code review and visual diff for Pull-Request&lt;/li&gt;
&lt;li&gt;NPM package export&lt;/li&gt;
&lt;li&gt;In browser or local CLI&lt;/li&gt;
&lt;li&gt;No tools or pipeline to deploy / manage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And best of all, we have a free plan to test Backlight. &lt;/p&gt;

&lt;p&gt;Let's give it a try ? :) &lt;a href="https://backlight.dev/"&gt;Start now&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Front-end is for heroes. At &lt;a href="https://divriots.com/"&gt;‹div›RIOTS&lt;/a&gt;, we believe front-end is a beautiful challenge and we are on a mission to empower front-end teams with great tools. Backlight is one of them. We also launched some free sandbox products to play with web components and tokens (&lt;a href="https://divriots.com/#what-we-do"&gt;check them out here&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>front</category>
      <category>dev</category>
      <category>design</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Our experience with Astro</title>
      <dc:creator>Georges Gomes</dc:creator>
      <pubDate>Fri, 09 Jul 2021 17:10:22 +0000</pubDate>
      <link>https://forem.com/divriots/our-experience-with-astro-1hk8</link>
      <guid>https://forem.com/divriots/our-experience-with-astro-1hk8</guid>
      <description>&lt;p&gt;We built &lt;a href="https://divRIOTS.com/" rel="noopener noreferrer"&gt;divRIOTS.com&lt;/a&gt; with &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;.&lt;br&gt;
"Here we go, another framework is out and another dude is making a website and blogging about it"&lt;/p&gt;

&lt;p&gt;Let's see if we can make this interesting. 😉&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Astro?
&lt;/h2&gt;

&lt;p&gt;If you already know &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, you can skip to the next chapter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; is a Static Site Generator (SSG). Build your pages in &lt;code&gt;.astro&lt;/code&gt; files (very close to HTML/JSX) and sparkle them with React/Vue/Preact/Svelte components. It deals with file-based routing and templates neutrally. Bring your component framework. &lt;cite&gt;In my words&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is more to it. See &lt;a href="https://astro.build/blog/introducing-astro" rel="noopener noreferrer"&gt;Astro's introduction blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you have time, there is a &lt;a href="https://www.learnwithjason.dev/ship-less-javascript-with-astro" rel="noopener noreferrer"&gt;90min video and Transcript&lt;/a&gt; about it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why we chose Astro
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Not because it's the new/latest shiny framework 😀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I shared my thought in April when I first saw &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;.&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;When it was time to develop the new &lt;a href="https://divRIOTS.com/" rel="noopener noreferrer"&gt;divRIOTS.com&lt;/a&gt; website, we searched for the best option.&lt;/p&gt;

&lt;p&gt;Our requirements were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt; - It's not going to be a massive site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs on JavaScript&lt;/strong&gt; - Ecosystem we know well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate 100% static HTML&lt;/strong&gt; - Good performance, good SEO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File-based routing&lt;/strong&gt; - Very convenient&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow component-driven development&lt;/strong&gt; - That's how we like to build&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown support&lt;/strong&gt; - For the blog posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are &lt;a href="https://jamstack.org/generators/" rel="noopener noreferrer"&gt;many static site generators&lt;/a&gt;.&lt;br&gt;
But, believe it or not, there aren't many options matching our requirements.&lt;/p&gt;

&lt;p&gt;Most component-driven options will come with some relatively heavy JavaScript&lt;br&gt;
payloads for &lt;a href="https://en.wikipedia.org/wiki/Hydration_(web_development)" rel="noopener noreferrer"&gt;hydration&lt;/a&gt;, even if the content is 100% static.&lt;/p&gt;

&lt;p&gt;On the other end, truly JavaScript-less SSG will use template-engine like &lt;a href="https://mozilla.github.io/nunjucks/" rel="noopener noreferrer"&gt;Nunjucks&lt;/a&gt; or &lt;a href="https://shopify.github.io/liquid/" rel="noopener noreferrer"&gt;Liquid&lt;/a&gt;. They are amazing options but it's another language and another paradigm. Not component-driven.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We just wanted a good Static Site Generator with a JavaScript Developper Experience and 100% HTML output by default. And &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; is precisely that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Pure Astro
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://divRIOTS.com/" rel="noopener noreferrer"&gt;divRIOTS.com&lt;/a&gt; is built in 100% &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;a href="https://reactjs.org" rel="noopener noreferrer"&gt;React&lt;/a&gt;, No &lt;a href="https://vuejs.org" rel="noopener noreferrer"&gt;Vue&lt;/a&gt;, No &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;, None of the &lt;a href="https://github.com/snowpackjs/astro#-partial-hydration" rel="noopener noreferrer"&gt;partial hydration&lt;/a&gt; or &lt;a href="https://jasonformat.com/islands-architecture/" rel="noopener noreferrer"&gt;islands&lt;/a&gt; capabilities of &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just &lt;code&gt;.astro&lt;/code&gt; files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You don't need to use the cutting-edge hydration capabilities of &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; to already benefit from it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; comes with an elegant component model and a solid CSS pipeline with Scoped CSS, CSS Modules, and Sass support.&lt;/p&gt;

&lt;p&gt;OUT-OF-THE-BOX.&lt;/p&gt;
&lt;h2&gt;
  
  
  Underrated feature
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;'s CSS Bundling is probably its most underrated feature. It never makes it to the headline but it's by far my favorite!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, you just layout &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags in your astro components where you need them and add lists of &lt;code&gt;&amp;lt;link ref="stylesheet"&amp;gt;&lt;/code&gt; in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://divRIOTS.com/" rel="noopener noreferrer"&gt;divRIOTS.com&lt;/a&gt; uses 2 global css in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; in the most idiomatic way.&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;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/css/reset.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/css/global.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these &lt;code&gt;.css&lt;/code&gt; files are minified and calling them separately doesn't provide the best performance result.&lt;/p&gt;

&lt;p&gt;But when built for production with &lt;code&gt;astro build&lt;/code&gt;, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags and &lt;code&gt;&amp;lt;link ref="stylesheet"&amp;gt;&lt;/code&gt; are minified and bundled automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a style only appears on one route, it’s only loaded for that route. (&lt;code&gt;/_astro/[page]-[hash].css&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If a style appears on multiple routes, it’s deduplicated into a (&lt;code&gt;\_astro/common-[hash].css&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In production, pages have:&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;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/_astro/common-[hash].css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/_astro/mypage-[hash].css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/_astro/common-[hash].css&lt;/code&gt; is the same on every page. It's cached and not re-downloaded during navigation on the site. It's hard to have a better result.&lt;/p&gt;

&lt;p&gt;This means that I can write styles the way it makes sense for readability and maintenance and let &lt;code&gt;astro build&lt;/code&gt; take care of best performance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't know any static site generator capable of doing [pure] CSS Bundling and minification so seamlessly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More details in &lt;a href="https://github.com/snowpackjs/astro/blob/main/docs/styling.md#-bundling" rel="noopener noreferrer"&gt;Astro's Styling Guide #bundling&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance results
&lt;/h2&gt;

&lt;p&gt;The output is 100% optimized HTML/CSS. It's hard to be slow 😀&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%2Fg0suko3raqa4kyt3rpcl.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%2Fg0suko3raqa4kyt3rpcl.jpg" alt="Mobile performance results (98)"&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finc12nwi92x87vpy684a.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%2Finc12nwi92x87vpy684a.jpg" alt="Desktop performance results (100)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's missing in Astro
&lt;/h2&gt;

&lt;p&gt;In my humble opinion, not much. &lt;a href="https://divRIOTS.com/" rel="noopener noreferrer"&gt;divRIOTS.com&lt;/a&gt; is proof of that.&lt;/p&gt;

&lt;p&gt;But here is my wish list:&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript processing
&lt;/h3&gt;

&lt;p&gt;Like &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;'s CSS Bundling, I would like my &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags transpiled, bundled, chunked, and minified in the best possible way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transpiled : I can write ES202X code and get a more compatible output.&lt;/li&gt;
&lt;li&gt;Bundled : I can import bare modules from &lt;code&gt;node_modules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chuncked : If modules are used on many pages, move them into a single &lt;code&gt;common-chunk.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Minified : Everybody wants small JavaScript - always.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, I don't need a &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;webpack&lt;/a&gt; or &lt;a href="https://gulpjs.com/" rel="noopener noreferrer"&gt;gulp&lt;/a&gt; configuration on top of &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/snowpackjs/astro/issues/370" rel="noopener noreferrer"&gt;GitHub issue #370&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Image processing
&lt;/h3&gt;

&lt;p&gt;Like JavaScript, image optimization is another fairly complex build process to add on top of static site generators. Having out-of-the-box support would help get maximum performance with minimal effort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/snowpackjs/astro/issues/492" rel="noopener noreferrer"&gt;GitHub Issue #492&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  "Permalink” for certain pages
&lt;/h3&gt;

&lt;p&gt;Today all pages are generated as &lt;code&gt;/slug/index.html&lt;/code&gt;, but some pages need to be generated as &lt;code&gt;/slug.html&lt;/code&gt; instead. Like &lt;code&gt;/404.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/snowpackjs/astro/issues/465" rel="noopener noreferrer"&gt;GitHub Issue #465&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; is more than a SSG&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As described in &lt;a href="https://twitter.com/georges_gomes/status/1380801812656226304" rel="noopener noreferrer"&gt;my tweet about Astro&lt;/a&gt;, another compelling feature of &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; is his neutrality to frameworks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; takes care of routing, layouts, data management and SSR infrastructure and you can bring your components from any other framework (currently &lt;a href="https://reactjs.org" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://vuejs.org" rel="noopener noreferrer"&gt;Vue3&lt;/a&gt;, &lt;a href="https://preactjs.com" rel="noopener noreferrer"&gt;Preact&lt;/a&gt;, and &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;) but still keep zero JavaScript runtime on the output if you want.&lt;/p&gt;

&lt;p&gt;It makes your site last longer as component frameworks come and go. It also makes your component last longer as you don't need to migrate them from one framework to another. Just use them as long as you want.&lt;/p&gt;

&lt;p&gt;I called &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; an "Agnostic Meta-Framework". And I think we will see other solutions emerge in this space because it makes a lot of sense to decouple the meta-frameworks from the rendering libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another Astro website is coming up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://backlight.dev" rel="noopener noreferrer"&gt;Backlight.dev&lt;/a&gt;, our upcoming product to build and manage Design Systems on the code-side, will be revealed soon.&lt;/p&gt;

&lt;p&gt;The full landing is also made in &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; but taking it to a whole new level 🚀&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stay tuned!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
