<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yoriiis</title>
    <description>The latest articles on DEV Community by Yoriiis (@yoriiis).</description>
    <link>https://dev.to/yoriiis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg</url>
      <title>DEV Community: Yoriiis</title>
      <link>https://dev.to/yoriiis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yoriiis"/>
    <language>en</language>
    <item>
      <title>Why I built vLitejs, a fast and flexible native video and audio player</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Sun, 07 Dec 2025 20:44:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/why-i-built-vlitejs-a-fast-and-flexible-native-video-and-audio-player-2c26</link>
      <guid>https://dev.to/yoriiis/why-i-built-vlitejs-a-fast-and-flexible-native-video-and-audio-player-2c26</guid>
      <description>&lt;p&gt;Nearly a decade ago, I started exploring ways to build lightweight and flexible video players. While working on client projects, I realized that existing libraries were often too heavy, difficult to customize, or included features that not everyone needed.&lt;/p&gt;

&lt;p&gt;This experience inspired the creation of &lt;strong&gt;&lt;a href="https://vlite.js.org" rel="noopener noreferrer"&gt;vLitejs&lt;/a&gt;&lt;/strong&gt; (pronounced &lt;em&gt;/viːlaɪt/&lt;/em&gt;), an open source project designed to offer a minimal, high-performance video and audio player that can be extended with plugins and providers.&lt;/p&gt;




&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;The first version of vLitejs was released in &lt;strong&gt;2019&lt;/strong&gt;, supporting &lt;strong&gt;HTML5 video&lt;/strong&gt; and &lt;strong&gt;YouTube&lt;/strong&gt;. Audio support was added shortly after, along with accessibility and keyboard navigation. Additional providers like &lt;strong&gt;Dailymotion&lt;/strong&gt; and &lt;strong&gt;Vimeo&lt;/strong&gt; were later integrated.&lt;/p&gt;

&lt;p&gt;The project has evolved with contributions from the community. Issues and discussions help refine the project. I guide the project's evolution, ensuring decisions follow a clear vision: requests are debated, sometimes implemented as plugins, or occasionally declined to maintain the project's focus, lightweight size, and performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Context and motivation
&lt;/h2&gt;

&lt;p&gt;vLitejs was born from the desire to have a lightweight, fast, and flexible player.&lt;/p&gt;

&lt;p&gt;At the time, libraries like Video.js and Plyr.js had grown heavy because they accepted perhaps too many requests from their communities, and maintenance became complex. I wanted a solution that stayed small by design while still being extensible when necessary.&lt;/p&gt;

&lt;p&gt;The goal was simple: keep the default player minimal and make any extra capabilities optional through plugins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design principles and architecture
&lt;/h2&gt;

&lt;p&gt;vLitejs is based on a clear and intentional separation: a minimal core, optional providers and plugins. The core implements only the essentials for HTML5 playback. Providers adapt the player to external platforms (YouTube, Dailymotion, Vimeo) and their SDKs are loaded on demand. Plugins add optional capabilities (PIP, subtitles, cast, hotkeys, etc.) in a scoped way that keeps the codebase modular and reusable.&lt;/p&gt;

&lt;p&gt;Key technical choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Written in &lt;strong&gt;TypeScript&lt;/strong&gt; for strong typing and maintainability&lt;/li&gt;
&lt;li&gt;Delivered as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules" rel="noopener noreferrer"&gt;EcmaScript Module (ESM)&lt;/a&gt; (inspired by Sindre Sorhus)&lt;/li&gt;
&lt;li&gt;Supports recent Node.js LTS releases and modern browsers&lt;/li&gt;
&lt;li&gt;Architecture based on a minimal core with opt-in plugins and providers&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Each provider inherits from a base &lt;code&gt;Player&lt;/code&gt; class, ensuring consistent behavior across playback types. Plugins interact with this class through the public API, extending the player without altering the core.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These architectural rules enable vLitejs to remain compact and easy to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;vLitejs provides a set of practical features designed for both developers and end users, all built around a modular architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML5 video &amp;amp; audio by default&lt;/li&gt;
&lt;li&gt;Accessibility and keyboard navigation&lt;/li&gt;
&lt;li&gt;Customizable control elements&lt;/li&gt;
&lt;li&gt;Unified events API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;YouTube&lt;/li&gt;
&lt;li&gt;Dailymotion&lt;/li&gt;
&lt;li&gt;Vimeo&lt;/li&gt;
&lt;li&gt;SDKs automatically loaded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Plugins (highlights)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Picture-in-Picture&lt;/li&gt;
&lt;li&gt;Subtitles&lt;/li&gt;
&lt;li&gt;Cast &amp;amp; AirPlay&lt;/li&gt;
&lt;li&gt;Hotkeys&lt;/li&gt;
&lt;li&gt;Sticky player&lt;/li&gt;
&lt;li&gt;Monetization (Google IMA SDK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HLS support for streaming&lt;/li&gt;
&lt;li&gt;Inline SVG icons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvxlh0j9gvxta0fc6jnl6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvxlh0j9gvxta0fc6jnl6.jpg" alt="vLitejs player"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;This section shows how to include and use vLitejs in your pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initializing the player
&lt;/h3&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;video&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"player"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;path_to_video_mp4&amp;gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vlitejs/vlite.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vlitejs&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;vlitejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#player&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;h3&gt;
  
  
  Providers example (YouTube)
&lt;/h3&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vlitejs/vlite.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vlitejs&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;vlitejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;VlitejsYoutube&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;vlitejs/providers/youtube.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VlitejsYoutube&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#player&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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;youtube&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;h3&gt;
  
  
  Plugins example (PIP + subtitle)
&lt;/h3&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vlitejs/vlite.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vlitejs/plugins/pip.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vlitejs/plugins/subtitle.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vlitejs&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;vlitejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;VlitejsPip&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;vlitejs/plugins/pip.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;VlitejsSubtitle&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;vlitejs/plugins/subtitle.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VlitejsPip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VlitejsSubtitle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#player&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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Global events
&lt;/h3&gt;

&lt;p&gt;All events are standardized across providers and plugins. You can listen to common events such as &lt;code&gt;play&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt;, &lt;code&gt;timeupdate&lt;/code&gt;, and many more depending on the context.&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;new&lt;/span&gt; &lt;span class="nc"&gt;Vlitejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#player&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;onReady&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;play&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Video started&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;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pause&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Video paused&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;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeupdate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Current time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentTime&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;h3&gt;
  
  
  Creating a custom plugin
&lt;/h3&gt;

&lt;p&gt;Developers can create custom plugins to extends capabilities. Plugins are scoped, interact with the player instance, and can be registered via the Plugin API.&lt;/p&gt;

&lt;p&gt;For more details, see the &lt;a href="https://github.com/vlitejs/vlite/blob/main/src/plugins/README.md" rel="noopener noreferrer"&gt;Plugin API documentation&lt;/a&gt; and &lt;a href="https://github.com/vlitejs/vlite/blob/main/src/providers/README.md" rel="noopener noreferrer"&gt;Provider API documentation&lt;/a&gt; on GitHub. They describe the available lifecycle hooks and best practices for building custom behaviors.&lt;/p&gt;




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

&lt;p&gt;vLitejs will keep evolving while remaining lightweight and modular. Planned improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated E2E tests with Playwright to verify core and plugin integrations&lt;/li&gt;
&lt;li&gt;New providers and plugins driven by real user demand&lt;/li&gt;
&lt;li&gt;Better documentation and examples for plugin/provider development&lt;/li&gt;
&lt;li&gt;Performance monitoring and selective optimizations as needed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A clear &lt;strong&gt;architecture enables optimal performance&lt;/strong&gt; while keeping the core minimal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularity allows customization&lt;/strong&gt; without overloading the library&lt;/li&gt;
&lt;li&gt;Strong &lt;strong&gt;TypeScript typing&lt;/strong&gt; ensures maintainability and reliability&lt;/li&gt;
&lt;li&gt;Supports only modern browsers, avoiding unnecessary legacy code&lt;/li&gt;
&lt;li&gt;Community input is valuable, but decisions follow a clear vision to keep the project focused and maintainable&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;vLitejs is a personal open source project that gives you a fast, flexible video and audio player. Its modular design lets you include only what you need: a simple, reliable player by default, with advanced features available on demand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vlite.js.org" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Discover vLitejs&lt;/a&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❤️ Special thanks to Maxime Lerouge for his help at the beginning of the project and Victor Schirm for designing the logo.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vlitejs/vlite" rel="noopener noreferrer"&gt;vLitejs on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video" rel="noopener noreferrer"&gt;The Video embed element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/audio" rel="noopener noreferrer"&gt;The Audio embed element&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>video</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>From ESLint/StyleLint and Prettier to Biome: simplifying our front-end linting</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Thu, 27 Nov 2025 21:14:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/from-eslintstylelint-and-prettier-to-biome-simplifying-our-front-end-linting-3nmj</link>
      <guid>https://dev.to/yoriiis/from-eslintstylelint-and-prettier-to-biome-simplifying-our-front-end-linting-3nmj</guid>
      <description>&lt;p&gt;When you work on multiple front-end projects at scale, consistency and automation quickly become key.&lt;/p&gt;

&lt;p&gt;At Prisma Media, we wanted every merge request to focus on actual logic changes, not formatting or stylistic details.&lt;/p&gt;

&lt;p&gt;We had relied on the classic linting stack: ESLint for JavaScript and TypeScript, StyleLint for CSS and Prettier for consistent formatting.&lt;/p&gt;

&lt;p&gt;With VS Code Workspaces configured for auto-format on save, code reviews became cleaner and formatting discussions disappeared.&lt;/p&gt;

&lt;p&gt;This article explains how we simplified this stack by migrating to Biome, a single tool that handles linting and formatting across multiple front-end languages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges with ESLint, StyleLint and Prettier
&lt;/h2&gt;

&lt;p&gt;Before Biome, our front-end projects relied on the classic trio: &lt;strong&gt;ESLint&lt;/strong&gt;, &lt;strong&gt;StyleLint&lt;/strong&gt; and &lt;strong&gt;Prettier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Over time, the linting stack grew to cover more languages and use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript, TypeScript and JSX/TSX&lt;/li&gt;
&lt;li&gt;PostCSS for styling&lt;/li&gt;
&lt;li&gt;Markdown for documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This diversity came with a high maintenance cost.&lt;br&gt;
Our global configuration alone depended on more than 15 packages 🤯&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For ESLint: &lt;code&gt;eslint&lt;/code&gt;, &lt;code&gt;eslint-config-prettier&lt;/code&gt;, &lt;code&gt;eslint-config-standard&lt;/code&gt;, &lt;code&gt;eslint-plugin-import&lt;/code&gt;, &lt;code&gt;eslint-plugin-jsdoc&lt;/code&gt;, &lt;code&gt;eslint-plugin-n&lt;/code&gt;, &lt;code&gt;eslint-plugin-prettier&lt;/code&gt;, &lt;code&gt;eslint-plugin-promise&lt;/code&gt;, &lt;code&gt;eslint-plugin-react&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For TypeScript: &lt;code&gt;@typescript-eslint/eslint-plugin&lt;/code&gt;, &lt;code&gt;@typescript-eslint/parser&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For CSS: &lt;code&gt;stylelint&lt;/code&gt;, &lt;code&gt;stylelint-config-standard&lt;/code&gt;, &lt;code&gt;stylelint-prettier&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;And finally, &lt;code&gt;prettier&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Upgrading one dependency often required adjusting several others. In practice, we planned upgrades of these packages 1-2 times per year to ensure stability. For every update, we had to validate compatibility, update shared configs and fix CI pipelines. Maintaining this stack was becoming increasingly time-consuming and prone to errors.&lt;/p&gt;


&lt;h2&gt;
  
  
  Migration to Biome
&lt;/h2&gt;

&lt;p&gt;When &lt;strong&gt;Biome&lt;/strong&gt; appeared, the promise was appealing: &lt;strong&gt;one tool, one config, multiple languages&lt;/strong&gt;.&lt;br&gt;
A single binary capable of linting, formatting and analyzing code, with far less setup and impressive performance.&lt;/p&gt;

&lt;p&gt;I started by testing Biome on my &lt;a href="https://github.com/yoriiis" rel="noopener noreferrer"&gt;open source projects&lt;/a&gt; to evaluate effectiveness and flexibility, then gradually introduced it into internal front-end apps.&lt;br&gt;
The first results were convincing: Biome matched ESLint, StyleLint and Prettier in functionality, but everything was now configured in a single file instead of juggling multiple configs and parser versions.&lt;/p&gt;

&lt;p&gt;A simplified version of our &lt;code&gt;biome.json&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://biomejs.dev/schemas/2.2.7/schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"includes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"formatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"indentStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lineWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"linter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recommended"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"formatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quoteStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"javascript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"formatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quoteStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"semicolons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asNeeded"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This single configuration file replaced the entire ESLint, StyleLint and Prettier stack.&lt;/p&gt;

&lt;p&gt;We also updated our VS Code workspace to integrate Biome, enabling format-on-save and automatic code actions:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"biomejs.biome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"[javascript][typescript][typescriptreact][css][json]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.codeActionsOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quickfix.biome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"explicit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source.organizeImports.biome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"explicit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source.fixAll.biome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"explicit"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;💡 Biome also provides a &lt;a href="https://biomejs.dev/guides/migrate-eslint-prettier" rel="noopener noreferrer"&gt;migration tool&lt;/a&gt; to help transition from ESLint and Prettier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When &lt;a href="https://biomejs.dev/blog/biome-v2" rel="noopener noreferrer"&gt;Biome v2&lt;/a&gt; was released, we quickly migrated. The new plugin API looked promising and offered more flexibility for custom rules.&lt;br&gt;
One feature we immediately started using is the option to &lt;a href="https://biomejs.dev/assist/actions/organize-imports" rel="noopener noreferrer"&gt;organize imports&lt;/a&gt; automatically.&lt;/p&gt;


&lt;h2&gt;
  
  
  Biome in CI/CD
&lt;/h2&gt;

&lt;p&gt;One of the biggest improvements was how easily Biome integrated into our &lt;strong&gt;GitLab CI pipelines&lt;/strong&gt;.&lt;br&gt;
As a single binary, it eliminated the need for multiple npm installs and complex config merges.&lt;br&gt;
Adding linting to a pipeline became as simple as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;biome check &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The performance gain was immediate.&lt;br&gt;
Linting jobs now run faster and more consistently across all environments, reducing CI runtime and minimizing potential inconsistencies between local development and pipelines.&lt;/p&gt;


&lt;h2&gt;
  
  
  ESLint vs Biome — simplified comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;ESLint + StyleLint + Prettier&lt;/th&gt;
&lt;th&gt;Biome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tools &amp;amp; dependencies&lt;/td&gt;
&lt;td&gt;~15 separate packages to maintain&lt;/td&gt;
&lt;td&gt;1 single binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Multiple config files &amp;amp; shared extensions&lt;/td&gt;
&lt;td&gt;Single &lt;code&gt;biome.json&lt;/code&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Slower: separate tools for JS, TS, CSS, parsing overhead&lt;/td&gt;
&lt;td&gt;Faster: unified binary, single parsing pass, lower memory usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDE integration&lt;/td&gt;
&lt;td&gt;Requires multiple extensions&lt;/td&gt;
&lt;td&gt;Single VS Code plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-language support&lt;/td&gt;
&lt;td&gt;JS, TS, JSX/TSX, CSS via separate plugins&lt;/td&gt;
&lt;td&gt;JS, TS, JSX/TSX, CSS, JSON and more to come soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD setup&lt;/td&gt;
&lt;td&gt;Complex npm-based scripts and merges&lt;/td&gt;
&lt;td&gt;Lightweight, single command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance overhead&lt;/td&gt;
&lt;td&gt;High: multiple packages, compatibility checks&lt;/td&gt;
&lt;td&gt;Minimal: single upgrade keeps everything aligned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Key notes
&lt;/h2&gt;

&lt;p&gt;After several months using Biome in production, the benefits are clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One tool, one config — simpler setup and updates&lt;/li&gt;
&lt;li&gt;More thorough linting — immediate feedback in the editor with highlighting and tooltips&lt;/li&gt;
&lt;li&gt;Improved onboarding — developers install Biome and start coding quickly&lt;/li&gt;
&lt;li&gt;Consistent rules across JS, TS and CSS&lt;/li&gt;
&lt;li&gt;Excellent IDE integration out of the box&lt;/li&gt;
&lt;li&gt;Accessibility checks included by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are interested in accessibility tooling, check out our related article on&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7" class="crayons-story__hidden-navigation-link"&gt;Improving accessibility for popins: focus trap and keyboard handling&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/yoriiis" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" alt="yoriiis profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/yoriiis" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Yoriiis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Yoriiis
                
              
              &lt;div id="story-author-preview-content-2950465" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/yoriiis" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Yoriiis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 12 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7" id="article-link-2950465"&gt;
          Improving accessibility for popins: focus trap and keyboard handling
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/a11y"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;a11y&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Maintaining the front-end stack has become much easier.&lt;br&gt;
Now, a single Biome upgrade keeps linting, formatting and analysis aligned across all projects.&lt;/p&gt;


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

&lt;p&gt;Migrating from ESLint, StyleLint and Prettier to Biome simplified our front-end tooling and unified our workflow, while drastically reducing maintenance overhead.&lt;/p&gt;

&lt;p&gt;Biome's evolution and plugin ecosystem provide a solid foundation for our future front-end architecture. Its speed, simplicity and improved developer experience make it an ideal choice for large-scale front-end projects.&lt;/p&gt;

&lt;p&gt;Full configuration files and examples are available in GitHub&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/biome-migration" class="crayons-btn crayons-btn--primary"&gt;Discover our Biome config on GitHub&lt;/a&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://biomejs.dev" rel="noopener noreferrer"&gt;Biome official documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/editing/workspaces/workspaces" rel="noopener noreferrer"&gt;VS Code workspace documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>javascript</category>
      <category>biome</category>
      <category>linting</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Improving accessibility for popins: focus trap and keyboard handling</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Wed, 12 Nov 2025 22:28:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7</link>
      <guid>https://dev.to/yoriiis/improving-accessibility-for-popins-focus-trap-and-keyboard-handling-50k7</guid>
      <description>&lt;p&gt;Keyboard users often encounter issues when interacting with popins, modals, or tooltips. During an accessibility audit with a partner, we noticed that focus can easily escape the popin, leading to a confusing experience.&lt;/p&gt;

&lt;p&gt;This approach ensures keyboard users can stay within the popin and interact predictably, improving accessibility and user experience (UX) for all users.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Popins generally include several interactive elements: a close button, overlay, and main content such as forms or call-to-actions (CTAs). Without proper focus management, users can tab out of the popin into the underlying page, breaking accessibility rules and making interactions unpredictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Key rules for accessible popins:&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial focus on the close button
&lt;/h3&gt;

&lt;p&gt;When a popin opens, we save the currently focused element and programmatically set focus to the close button using &lt;code&gt;.focus()&lt;/code&gt;. This ensures that keyboard users start interacting with the popin immediately and allows us to restore focus later when the popin closes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap focus inside the popin
&lt;/h3&gt;

&lt;p&gt;Tabulation should only traverse elements inside the popin. The focus remains trapped &lt;strong&gt;only while the popin is open&lt;/strong&gt;. Reaching the last element and pressing ⇥ Tab returns to the first, and ⇧ Shift + ⇥ Tab from the first moves to the last.&lt;/p&gt;

&lt;p&gt;When the popin is closed, focus is restored to the element that triggered its opening.&lt;/p&gt;

&lt;p&gt;If the popin opens automatically without user interaction, do not move focus; instead, ensure the popin is keyboard-accessible and optionally notify the user via an ARIA live region.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visible focus styling
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;:focus-visible&lt;/code&gt; ensures keyboard users can see which element is active. This pseudo-class only applies when interacting via keyboard, avoiding unnecessary outlines for mouse users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 A good practice is to allow the popin to be closed by pressing the Escape or clicking on the overlay.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here's a minimal example of a popin with HTML, CSS, and JS implementing the focus trap pattern:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/yoriiis/embed/MYKGXpw?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML
&lt;/h3&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;role=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt; &lt;span class="na"&gt;aria-modal=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"newsletter-title"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"popin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"popin-close"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Close newsletter popin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;×&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"newsletter-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Subscribe to our newsletter&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Subscribe&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CSS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.popin&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#007acc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&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;
  
  
  JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FocusTrap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastElement&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActiveElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusableSelector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button:not([disabled]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleKeydown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActiveElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusableContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusableSelector&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Recalculate focusable elements&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusableContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusableSelector&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;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastElement&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusableContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&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="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActiveElement&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&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;h2&gt;
  
  
  Key points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Always set initial focus to the most relevant interactive element (usually the close button)&lt;/li&gt;
&lt;li&gt;Trap focus inside popins to prevent keyboard users from leaving the overlay unintentionally&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;:focus-visible&lt;/code&gt; to ensure the active element is clearly visible for keyboard navigation&lt;/li&gt;
&lt;li&gt;Maintain correct DOM order and define &lt;code&gt;tabindex&lt;/code&gt; for logical navigation&lt;/li&gt;
&lt;li&gt;Include proper ARIA roles and attributes (&lt;code&gt;role="dialog"&lt;/code&gt;, &lt;code&gt;aria-modal="true"&lt;/code&gt;, &lt;code&gt;aria-labelledby&lt;/code&gt;, button types)&lt;/li&gt;
&lt;li&gt;Restore focus to the triggering element after closing the popin&lt;/li&gt;
&lt;li&gt;Handle automatically opened popins appropriately (keyboard-accessible, ARIA live notification)&lt;/li&gt;
&lt;li&gt;Modularize the focus trap pattern for reuse across multiple popins&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Focus management for popins is a small but crucial detail in accessibility. Implementing a focus trap provides a predictable experience for keyboard users and can prevent common accessibility pitfalls.&lt;/p&gt;

&lt;p&gt;Thinking about focus management early in your modal design ensures a smoother and more consistent experience for all users, and the pattern is reusable across multiple sites or projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html" rel="noopener noreferrer"&gt;WCAG 2.1 - Keyboard accessibility (2.1.1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-order.html" rel="noopener noreferrer"&gt;WCAG 2.1 - Focus order (2.4.3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal" rel="noopener noreferrer"&gt;Dialog (Modal) pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/focus-trap/focus-trap" rel="noopener noreferrer"&gt;&lt;code&gt;focus-trap&lt;/code&gt; GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>a11y</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How we test NPM packages before publishing with npm pack</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Sun, 26 Oct 2025 21:54:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/how-we-test-npm-packages-before-publishing-with-npm-pack-57bo</link>
      <guid>https://dev.to/yoriiis/how-we-test-npm-packages-before-publishing-with-npm-pack-57bo</guid>
      <description>&lt;p&gt;When working on multiple interdependent NPM packages across projects, testing a new version before publishing is essential.&lt;/p&gt;

&lt;p&gt;At Prisma Media, we maintain several packages on our private NPM registry, used directly on our websites. While each package comes with a standalone demo, we regularly need to validate development versions in real environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding &lt;code&gt;npm pack&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;npm pack&lt;/code&gt; builds your package locally and generates a &lt;code&gt;.tgz&lt;/code&gt; archive identical to what would be published with &lt;code&gt;npm publish&lt;/code&gt;. The archive is created in the current directory and includes only the files defined in the &lt;code&gt;files&lt;/code&gt; field of your &lt;code&gt;package.json&lt;/code&gt; or filtered by &lt;code&gt;.npmignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's also a convenient way to verify what your package will actually ship before publishing. Inspecting the generated archive helps ensure that only the expected files are included.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxdxpxrafmq6y8v9bu786.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxdxpxrafmq6y8v9bu786.png" alt=" raw `npm-pack` endraw  terminal output" width="800" height="732"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing options
&lt;/h2&gt;

&lt;p&gt;Developers have a few options to test packages locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local linking&lt;/strong&gt;: &lt;code&gt;npm link&lt;/code&gt; works if both the package and consuming project are cloned locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview or shared environments&lt;/strong&gt;: &lt;code&gt;npm link&lt;/code&gt; fails here. Other options include:

&lt;ul&gt;
&lt;li&gt;Publishing a tagged version (like &lt;code&gt;beta&lt;/code&gt;), it requires deployment to the registry&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;npm pack&lt;/code&gt;, it generates the &lt;code&gt;.tgz&lt;/code&gt; archive that would be published, without touching the registry&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;We chose &lt;code&gt;npm pack&lt;/code&gt; for its simplicity and safety. It allows developers and product teams to test new versions in local or preview environments, without affecting production workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Our package pipelines follow a standard sequence: &lt;code&gt;install&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;pack&lt;/code&gt; and finally &lt;code&gt;deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For details on our reusable GitLab CI template you can read&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0" class="crayons-story__hidden-navigation-link"&gt;Scaling GitLab CI/CD with reusable templates&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/yoriiis" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" alt="yoriiis profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/yoriiis" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Yoriiis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Yoriiis
                
              
              &lt;div id="story-author-preview-content-2908982" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/yoriiis" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Yoriiis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0" id="article-link-2908982"&gt;
          Scaling GitLab CI/CD with reusable templates
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gitlab"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gitlab&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ci"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ci&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;npm pack&lt;/code&gt; job is &lt;strong&gt;manual&lt;/strong&gt;. When a developer needs a dev version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the latest pipeline of the merge request&lt;/li&gt;
&lt;li&gt;Trigger the &lt;code&gt;npm-pack&lt;/code&gt; job&lt;/li&gt;
&lt;li&gt;The job runs and uploads the TGZ as an artifact&lt;/li&gt;
&lt;li&gt;Install and test locally:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Download and extract the artifact&lt;/li&gt;
&lt;li&gt;Install the TGZ in your project:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;path_to_tgz_file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This replaces the version from the registry with the local TGZ version. Example diff:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   - "player": "1.0.0"
   + "player": "file:player-1.1.0.tgz"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Commit the TGZ file to your branch &lt;strong&gt;if you plan to deploy a preview&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Once committed, the CI will install the TGZ&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitLab CI template
&lt;/h2&gt;

&lt;p&gt;Here is a simplified &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; template for the &lt;code&gt;npm-pack&lt;/code&gt; job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;npm-pack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;PACKAGES_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm-pack-packages'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22-alpine&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir $PACKAGES_DIR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PACKAGE_TGZ=$(npm pack)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mv "$PACKAGE_TGZ" $PACKAGES_DIR&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$PACKAGES_DIR&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This job generates the TGZ locally and stores it as an artifact. The file can then be installed for local or preview testing.&lt;/p&gt;

&lt;p&gt;For the GitHub Actions equivalent&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/npm-pack" class="crayons-btn crayons-btn--primary"&gt;Discover npm-pack workflow examples on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Developers can test packages locally &lt;strong&gt;without publishing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The TGZ can be versioned in the dev branch, allowing CI to install it on preview environments&lt;/li&gt;
&lt;li&gt;Product teams can validate features before merging&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Remember: &lt;strong&gt;TGZ files are for development and preview only&lt;/strong&gt;, never for production.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm pack&lt;/code&gt; replaces &lt;code&gt;npm link&lt;/code&gt; for shared or CI environments&lt;/li&gt;
&lt;li&gt;When switching back to a registry version, always run &lt;code&gt;npm uninstall &amp;lt;package&amp;gt;&lt;/code&gt; before reinstalling&lt;/li&gt;
&lt;li&gt;Version TGZ files only for temporary testing; never use them in production&lt;/li&gt;
&lt;li&gt;CI pipelines can safely produce TGZ artifacts for local or preview testing&lt;/li&gt;
&lt;li&gt;Manual triggering keeps pipelines efficient&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Using &lt;code&gt;npm pack&lt;/code&gt; to test packages locally or in preview environments simplifies cross-project development. It sits between local linking and full registry publishing, giving developers and product teams a faster way to validate changes.&lt;/p&gt;

&lt;p&gt;By integrating this workflow into CI, we can safely validate packages, maintain developer productivity and ensure quality before official publication.&lt;/p&gt;

&lt;p&gt;The complete example is available on GitHub for you to try out! 🧑‍💻&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/npm-pack" class="crayons-btn crayons-btn--primary"&gt;Discover NPM pack on GitHub&lt;/a&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/cli/v11/commands/npm-pack" rel="noopener noreferrer"&gt;&lt;code&gt;npm pack&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/cli/v11/commands/npm-link" rel="noopener noreferrer"&gt;&lt;code&gt;npm link&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/cli/v11/commands/npm-publish" rel="noopener noreferrer"&gt;&lt;code&gt;npm publish&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>npm</category>
      <category>node</category>
      <category>gitlab</category>
      <category>automation</category>
    </item>
    <item>
      <title>Scaling GitLab CI/CD with reusable templates</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Wed, 15 Oct 2025 21:44:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0</link>
      <guid>https://dev.to/yoriiis/scaling-gitlab-cicd-with-reusable-templates-5ho0</guid>
      <description>&lt;p&gt;Managing dozens of projects relying on GitLab CI/CD templates can be a challenge.&lt;/p&gt;

&lt;p&gt;At Prisma Media, on front-end libraries and some Symfony apps, we had a mix of configurations that were inconsistent, hard to maintain, and often not optimized. This article explains how we centralized our GitLab CI/CD pipelines into reusable, modular templates, and how you can adopt a similar approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a dedicated CI templates repository
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem: scattered CI configurations
&lt;/h3&gt;

&lt;p&gt;Before reusable templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each project had its own &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; with a lot of duplication&lt;/li&gt;
&lt;li&gt;Jobs were inconsistent across projects (&lt;code&gt;npm ci&lt;/code&gt; vs &lt;code&gt;npm install&lt;/code&gt;, different caching strategies)&lt;/li&gt;
&lt;li&gt;Pipelines were often slow and hard to debug&lt;/li&gt;
&lt;li&gt;Documentation was minimal or missing&lt;/li&gt;
&lt;li&gt;Synchronizes updates were complicated to deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Our solution: modular templates
&lt;/h3&gt;

&lt;p&gt;We created a &lt;strong&gt;dedicated repository&lt;/strong&gt; (&lt;code&gt;ci&lt;/code&gt;) hosting modular GitLab CI templates. Each template:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles a &lt;strong&gt;single task&lt;/strong&gt; (install dependencies, build, run tests, publish)&lt;/li&gt;
&lt;li&gt;Is optimized (cache, artifacts, retries)&lt;/li&gt;
&lt;li&gt;Is fully documented with usage examples and configurable variables&lt;/li&gt;
&lt;li&gt;Uses semantic versioning (&lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;) so projects can upgrade at their own pace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our templates are &lt;strong&gt;stage-agnostic&lt;/strong&gt;, with no predefined &lt;code&gt;needs&lt;/code&gt; or &lt;code&gt;stages&lt;/code&gt;, so consumer projects keep full control over their pipeline flow.&lt;/p&gt;

&lt;p&gt;Templates also support different working directory, depending on where NPM commands are executed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Tip: prefix template names (e.g. &lt;code&gt;.npm-install&lt;/code&gt;) to avoid conflicts with jobs in the consuming project.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Our template catalog
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Key features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm-install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Install dependencies&lt;/td&gt;
&lt;td&gt;Cache optimization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm-build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build application&lt;/td&gt;
&lt;td&gt;Custom command support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm-qa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run linter&lt;/td&gt;
&lt;td&gt;Custom command support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm-unit-test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run unit tests&lt;/td&gt;
&lt;td&gt;JUnit reports integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm-pack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create &lt;code&gt;.tgz&lt;/code&gt; package&lt;/td&gt;
&lt;td&gt;Artifact output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm-publish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publish to registry&lt;/td&gt;
&lt;td&gt;Manual trigger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create-tag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Git tag creation&lt;/td&gt;
&lt;td&gt;Version auto-detection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Each template is independent and hosted in a dedicated directory with its own documentation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pipeline overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fz8kblurvv0v47koutaol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fz8kblurvv0v47koutaol.png" alt="GitLab pipeline"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Shared configuration
&lt;/h2&gt;

&lt;p&gt;All templates extend a &lt;code&gt;common.yaml&lt;/code&gt; to centralize variables, cache, and default behaviors. This ensures consistency across templates and reduces duplication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MAIN_BRANCH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;NODE_IMAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22-alpine&lt;/span&gt;
  &lt;span class="na"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;

&lt;span class="na"&gt;.default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FF_USE_FASTZIP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt; &lt;span class="c1"&gt;# Optimize cache/artifact compression&lt;/span&gt;
  &lt;span class="na"&gt;interruptible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;expire_in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3 days&lt;/span&gt;
  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;runner_system_failure&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;stuck_or_timeout_failure&lt;/span&gt;

&lt;span class="na"&gt;.cache-npm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${WORKING_DIRECTORY}/package-lock.json&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${WORKING_DIRECTORY}/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Note on branch rules:&lt;br&gt;
All our templates use &lt;code&gt;$MAIN_BRANCH&lt;/code&gt; in rules instead of &lt;code&gt;$CI_DEFAULT_BRANCH&lt;/code&gt;.&lt;br&gt;
This ensures the pipeline behaves consistently relative to the versioned branch (&lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, etc.) rather than the repository's default branch, which may change over time.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Selected CI templates
&lt;/h2&gt;

&lt;p&gt;Here are a few representative templates; all templates are available in the &lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/gitlab-ci-templates"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npm-install&lt;/code&gt;: install dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.npm-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache-npm&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull-push&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${WORKING_DIRECTORY}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == 'merge_request_event'&lt;/span&gt;
        &lt;span class="s"&gt;|| $CI_COMMIT_REF_NAME == $MAIN_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;npm-build&lt;/code&gt;: build application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.npm-build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COMMAND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache-npm&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${WORKING_DIRECTORY}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${COMMAND}&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == 'merge_request_event'&lt;/span&gt;
        &lt;span class="s"&gt;|| $CI_COMMIT_REF_NAME == $MAIN_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;npm-qa&lt;/code&gt;: run linter (Biome)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.npm-qa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COMMAND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test:qa'&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache-npm&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${WORKING_DIRECTORY}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${COMMAND}&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == 'merge_request_event'&lt;/span&gt;
        &lt;span class="s"&gt;|| $CI_COMMIT_REF_NAME == $MAIN_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;npm-unit-test&lt;/code&gt;: run unit tests (Jest)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.npm-unit-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;COMMAND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test:unit'&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache-npm&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${WORKING_DIRECTORY}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${COMMAND}&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;junit.xml&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;junit.xml&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == 'merge_request_event'&lt;/span&gt;
        &lt;span class="s"&gt;|| $CI_COMMIT_REF_NAME == $MAIN_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 The job improves unit test report in GitLab merge request with &lt;a href="https://github.com/jest-community/jest-junit" rel="noopener noreferrer"&gt;&lt;code&gt;jest-junit&lt;/code&gt;&lt;/a&gt; package and &lt;a href="https://docs.gitlab.com/ci/testing/unit_test_reports/#view-test-results-in-merge-requests" rel="noopener noreferrer"&gt;GitLab unit test reports&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npm-pack&lt;/code&gt;: generate local package
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.npm-pack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;PACKAGES_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm-pack-packages'&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.cache-npm&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${WORKING_DIRECTORY}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir $PACKAGES_DIR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PACKAGE_TGZ=$(npm pack)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mv "$PACKAGE_TGZ" $PACKAGES_DIR&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$WORKING_DIRECTORY/$PACKAGES_DIR&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == 'merge_request_event'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Use this template for local testing before publishing or integration validation. The local NPM package can be installed in your project for testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npm-publish&lt;/code&gt;: publish to NPM registry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.npm-publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm publish&lt;/span&gt;
  &lt;span class="na"&gt;allow_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_REF_NAME == $MAIN_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 To publish to a private registry, update the &lt;code&gt;.npmrc&lt;/code&gt; file with the registry URL and authentication token before running the publish command.&lt;/p&gt;

&lt;p&gt;⚠ As of September 2025, NPM enforces shorter token lifetimes for write operations and plans to retire legacy tokens. To future-proof your CI, consider migrating to &lt;a href="https://docs.npmjs.com/trusted-publishers" rel="noopener noreferrer"&gt;Trusted Publishing&lt;/a&gt; (OIDC).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;create-tag&lt;/code&gt;: create Git tag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.create-tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.default&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22-alpine&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apk add --no-cache git&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.email "gitlab@gitlab.com"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.name "GitLab CI"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VERSION_NUMBER=$(npm pkg get version | xargs echo)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git tag $VERSION_NUMBER&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git remote set-url origin "${CI_SERVER_PROTOCOL}://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/$CI_PROJECT_PATH.git"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git push origin $VERSION_NUMBER&lt;/span&gt;
  &lt;span class="na"&gt;allow_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_REF_NAME == $MAIN_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Requires a Project Access Token in &lt;code&gt;GITLAB_TOKEN&lt;/code&gt; variable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final consumer usage
&lt;/h2&gt;

&lt;p&gt;In the consumer project, the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; becomes very simple. The project only imports the templates it needs and defines the dependencies between jobs itself. This keeps the pipeline clean, flexible, and easy to maintain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user/ci'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;templates.yaml'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_TAG&lt;/span&gt; &lt;span class="c1"&gt;# Disable tag pipeline&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.'&lt;/span&gt;

&lt;span class="na"&gt;npm-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.npm-install&lt;/span&gt;

&lt;span class="na"&gt;npm-build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.npm-build&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;npm-install&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;npm-qa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.npm-qa&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;npm-install&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;npm-unit-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.npm-unit-test&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;npm-install&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;npm-pack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.npm-pack&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;npm-build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;npm-publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.npm-publish&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;npm-build&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;npm-qa&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;npm-unit-test&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;create-tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.create-tag&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;npm-publish&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Versioning and evolution
&lt;/h2&gt;

&lt;p&gt;We tag template releases (&lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, etc.) so teams can upgrade on their schedule. Each release includes a changelog and migration notes for breaking changes. We also use code owners for approval deployment to secure our templates.&lt;/p&gt;

&lt;p&gt;Planned improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consider GitLab CI Components in the future for even better reuse&lt;/li&gt;
&lt;li&gt;Extend templates for other ecosystems (Python, PHP) as needed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Results and lessons learned
&lt;/h2&gt;

&lt;p&gt;For the projects that adopted the templates we observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Significant reduction in per-repo CI maintenance effort&lt;/li&gt;
&lt;li&gt;Faster onboarding for new projects and engineers&lt;/li&gt;
&lt;li&gt;More consistent build and publish behavior across teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Operational notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Template bugs can affect consumers widely, but fixing a template fixes it for all consumers at once&lt;/li&gt;
&lt;li&gt;Adoption takes time: provide migration guides and a short period of backward compatibility&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Centralizing GitLab CI templates gives us modular, documented, and versioned building blocks for pipelines. It reduces duplicated effort, enforces sensible defaults, and lets teams focus on product work rather than CI boilerplate.&lt;/p&gt;

&lt;p&gt;If you'd like to try the repository and examples, the code is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/gitlab-ci-templates" class="crayons-btn crayons-btn--primary"&gt;Discover our reusable CI templates on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;What's your current CI approach? Have you centralized templates, or do you prefer per-repo pipelines? I'd be interested to read your experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/ci/components" rel="noopener noreferrer"&gt;GitLab CI/CD components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/changelog/2025-09-29-strengthening-npm-security-important-changes-to-authentication-and-token-management/" rel="noopener noreferrer"&gt;Strengthening npm security: Important changes to authentication and token management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/trusted-publishers" rel="noopener noreferrer"&gt;Trusted publishing for npm packages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>devops</category>
      <category>gitlab</category>
      <category>ci</category>
      <category>automation</category>
    </item>
    <item>
      <title>Efficient script loading strategy</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Sun, 28 Sep 2025 22:08:24 +0000</pubDate>
      <link>https://dev.to/yoriiis/efficient-script-loading-strategy-12ik</link>
      <guid>https://dev.to/yoriiis/efficient-script-loading-strategy-12ik</guid>
      <description>&lt;p&gt;Loading JavaScript efficiently is critical for any website's performance.&lt;/p&gt;

&lt;p&gt;At Prisma Media, we made this transition several years ago, and the solution we use today remains reliable and effective across all our sites.&lt;/p&gt;

&lt;p&gt;Our pages must load scripts in a defined order while bundling resources efficiently.&lt;/p&gt;

&lt;p&gt;The typical sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consent management&lt;/li&gt;
&lt;li&gt;User connection&lt;/li&gt;
&lt;li&gt;Site resources (e.g. styles, scripts)&lt;/li&gt;
&lt;li&gt;Third-party scripts (e.g. Google Tag Manager, analytics)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Before 🦖: a JavaScript loader to control execution
&lt;/h2&gt;

&lt;p&gt;Back in 2018 we relied on &lt;a href="https://www.npmjs.com/package/scriptjs" rel="noopener noreferrer"&gt;scriptjs&lt;/a&gt;, a small library that lets you load scripts programmatically and control execution order. We placed our calls to &lt;code&gt;scriptjs&lt;/code&gt; in a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag just before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; and listed each resource in the exact order we needed.&lt;/p&gt;

&lt;p&gt;But two problems became clear over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance: page rendering was delayed&lt;/li&gt;
&lt;li&gt;Maintenance: the library was no longer actively maintained&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those issues pushed us to rethink script loading entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Back to basics
&lt;/h2&gt;

&lt;p&gt;After moving away from a JavaScript loader, our goal was to rely on the most &lt;strong&gt;native browser features&lt;/strong&gt; possible. The reasoning was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The closer we stay to native HTML and browser standards, the more aligned we are with long-term best practices&lt;/li&gt;
&lt;li&gt;We no longer wanted to depend on a third-party library that might require another migration in a few years&lt;/li&gt;
&lt;li&gt;The solution had to be sustainable and easy to deploy across all our websites&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The obvious choice was to use regular &lt;code&gt;&amp;lt;script src="…"&amp;gt;&lt;/code&gt; tags with the right attributes.&lt;/p&gt;

&lt;p&gt;As explained brilliantly in &lt;a href="https://flaviocopes.com/javascript-async-defer" rel="noopener noreferrer"&gt;Flavio Copes' article on &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;defer&lt;/code&gt;&lt;/a&gt;, the best strategy is to place scripts in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; &lt;strong&gt;with&lt;/strong&gt; the &lt;code&gt;defer&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;This placement enables scripts to download in parallel while the browser continues parsing. It ensures scripts execute in order after parsing and makes the DOM interactive sooner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling our loading sequence
&lt;/h2&gt;

&lt;p&gt;For scripts such as consent management, user connection, or third-party scripts served from a CDN, the transition was straightforward. We simply added &lt;code&gt;&amp;lt;script defer src="…"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; in the desired order.&lt;/p&gt;

&lt;p&gt;The bigger challenge came from our own resources. Our platform is a multi-page application (MPA), not a single-page app (SPA). Our websites are server-rendered with Twig templates and we use Webpack to package all resources (styles, scripts, etc.).&lt;/p&gt;

&lt;p&gt;Back in 2018 we had a single entry point for the entire site. That setup generated a large JavaScript file shared across all pages, even though each page had different needs. There was no code splitting or shared-chunk optimization.&lt;/p&gt;

&lt;p&gt;When we decided to rely on native &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, we wanted to align with our MPA architecture by producing page-specific bundles instead of a single monolithic file. Generating the right &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags for each page could not be done manually, so we needed a webpack plugin to generate them automatically at build time.&lt;/p&gt;

&lt;p&gt;At that time the popular &lt;a href="https://github.com/jantimon/html-webpack-plugin" rel="noopener noreferrer"&gt;html-webpack-plugin&lt;/a&gt; assumed a JavaScript-based template and didn't fit our needs.&lt;/p&gt;

&lt;p&gt;To bridge the gap I created &lt;a href="https://github.com/yoriiis/chunks-webpack-plugin" rel="noopener noreferrer"&gt;chunks-webpack-plugin&lt;/a&gt;: a Webpack plugin that analyzes entry point dependencies and generates HTML fragments with the appropriate &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;This kept the approach fully native, just standard &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; elements, while enabling per-page bundles with granular chunk splitting. For details on that optimization step you can read&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" class="crayons-story__hidden-navigation-link"&gt;Granular chunks and JavaScript modules for faster page loads&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/yoriiis" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" alt="yoriiis profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/yoriiis" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Yoriiis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Yoriiis
                
              
              &lt;div id="story-author-preview-content-329158" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/yoriiis" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Yoriiis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 10 '20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" id="article-link-329158"&gt;
          Granular chunks and JavaScript modules for faster page loads
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webperf"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webperf&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webpack"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webpack&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  💡 Key notes:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Modules: &lt;code&gt;&amp;lt;script type="module"&amp;gt;&lt;/code&gt; are deferred by default, the &lt;code&gt;defer&lt;/code&gt; attribute is unnecessary&lt;/li&gt;
&lt;li&gt;Legacy fallback: use &lt;code&gt;nomodule&lt;/code&gt; attribute for polyfills or legacy bundles; modern browsers will skip them automatically&lt;/li&gt;
&lt;li&gt;Attribute precedence: if both &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;defer&lt;/code&gt; are set, modern browsers give priority to &lt;code&gt;async&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inline scripts: &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;defer&lt;/code&gt; have no effect on inline scripts (those without a &lt;code&gt;src&lt;/code&gt; attribute)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  HTML rendering
&lt;/h2&gt;

&lt;p&gt;Below is a simplified example of the final markup. Each script follows the execution order described above.&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"consent.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"user-connection.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Page-specific bundle generated by Webpack --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"home.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"gtm.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Page-specific content goes here --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Maintaining efficient and reliable script loading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Performance: measurable improvements in First Contentful Paint (FCP) and in Largest Contentful Paint (LCP), thanks to deferred, non-blocking script loading&lt;/li&gt;
&lt;li&gt;Standards and maintainability: scripts are delivered as standard &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; elements, generated by Webpack, ensuring consistent behavior across all pages&lt;/li&gt;
&lt;li&gt;Best practices: we consistently favor &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags with a &lt;code&gt;src&lt;/code&gt; attribute over inline JavaScript blocks. This makes scripts cacheable, easier to debug, and aligned with browser parsing rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining native &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags and a controlled build-time generation, we ensure predictable execution, page-specific bundles, and a scalable, reliable strategy across all websites.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>webperf</category>
      <category>javascript</category>
      <category>webpack</category>
    </item>
    <item>
      <title>Managing private TypeScript types: beyond DefinitelyTyped</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Mon, 15 Sep 2025 21:25:41 +0000</pubDate>
      <link>https://dev.to/yoriiis/managing-private-typescript-types-beyond-definitelytyped-4lk3</link>
      <guid>https://dev.to/yoriiis/managing-private-typescript-types-beyond-definitelytyped-4lk3</guid>
      <description>&lt;p&gt;TypeScript gives us strong typing and we often rely on &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped" rel="noopener noreferrer"&gt;DefinitelyTyped&lt;/a&gt; for public libraries types. But as soon as you load libraries via a CDN or work on multiple internal projects that don't ship their own types, things start to break down. Each project defines its own version, duplication appears, and TypeScript starts yelling at you.&lt;/p&gt;

&lt;p&gt;In large organizations, this often happens: you want all projects to share the same types, but in reality, each team often reinvents its own.&lt;/p&gt;

&lt;p&gt;The solution? A centralized internal repository of TypeScript types.&lt;/p&gt;




&lt;h2&gt;
  
  
  How types propagate: NPM vs CDN
&lt;/h2&gt;

&lt;p&gt;When you install a package from NPM that ships its own types, everything works out of the box. But when you use a library via a CDN, TypeScript can't infer anything. You have to declare the types manually, often by extending &lt;code&gt;Window&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's when problems appear. One team declares a subset of methods, another team declares slightly different ones. Merge those projects and TypeScript won't know which definition to trust.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Loading a script via a CDN rather than via NPM depends on the use case. Sometimes the script does not exist on NPM, sometimes you want to prioritise a specific execution order.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem: conflicting or missing types
&lt;/h2&gt;

&lt;p&gt;Take the example of the following script loaded via a CDN, in this case the &lt;a href="https://developers.dailymotion.com/guides/getting-started-with-web-sdk" rel="noopener noreferrer"&gt;Dailymotion Web SDK&lt;/a&gt;.&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;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://geo.dailymotion.com/player.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exposes a global object &lt;code&gt;window.dailymotion&lt;/code&gt;. In TypeScript you'd declare it like this according to your requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;dailymotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;createPlayer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="na"&gt;selectorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;callback&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;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;PLAYER_START&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;PLAYER_END&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="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;Now imagine two projects that use the Dailymotion SDK and do not declare exactly the same type. If you merge the two, conflicts will arise. This is often unavoidable because each writes its own version, which may differ.&lt;/p&gt;

&lt;p&gt;In the event of a conflict, the following TypeScript error may appear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;error TS2717: Subsequent property declarations must have the same type.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See details of &lt;a href="https://typescript.tv/errors/#ts2717" rel="noopener noreferrer"&gt;error TS2717 on TypeScript.tv&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Centralizing types: the concept
&lt;/h2&gt;

&lt;p&gt;The solution is to centralize all internal and CDN-related type definitions in a single monorepo. Think of it as your private &lt;code&gt;DefinitelyTyped&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Benefits are immediate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: all projects use the same definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability&lt;/strong&gt;: publish once, install everywhere from your private registry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt;: no more mismatched types between projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance&lt;/strong&gt;: fix a type once, all consumers get the update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At Prisma Media, we call our monorepo &lt;strong&gt;Prime-Types&lt;/strong&gt; 🤖&lt;/p&gt;




&lt;h2&gt;
  
  
  Minimal implementation
&lt;/h2&gt;

&lt;p&gt;A minimal monorepo structure could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package.json  // defines workspaces
types/
  dailymotion/
    package.json
    index.d.ts
    README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root &lt;code&gt;package.json&lt;/code&gt; of the monorepo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./types/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc --noEmit --watch"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5.8.3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;types/dailymotion/package.json:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@prime-types/dailymotion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TypeScript types for Dailymotion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./index.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./index.d.ts"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./index.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./index.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5.8.3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example type definition for Dailymotion Web SDK in &lt;code&gt;index.d.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /types/dailymotion/index.d.ts&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;dailymotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;createPlayer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="na"&gt;selectorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;callback&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;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;PLAYER_START&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;PLAYER_END&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="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;// Export to ensure the file is treated as a module&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Add &lt;code&gt;export {}&lt;/code&gt; at the end of the file if it does not export any types or if only a &lt;code&gt;declare global&lt;/code&gt; exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To consume these types in a project, install the package in peer-dependencies preferably (see below why in this section).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @prime-types/dailymotion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, TypeScript searches for declarations in the &lt;code&gt;node_modules/@types&lt;/code&gt; path and in your project source. The rest of &lt;code&gt;node_modules&lt;/code&gt; is ignored. You must therefore add the path to your declaration files in the &lt;code&gt;tsconfig.json&lt;/code&gt; to allow TypeScript to reference and propagate them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
  "include": [
    "./src/**/*",
&lt;span class="gi"&gt;+   "./node_modules/@prime-types"
&lt;/span&gt;  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👀 Discover this example in the minimalist monorepo &lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/prime-types"&gt;Prime Types on GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Managing multiple versions: peer dependencies
&lt;/h2&gt;

&lt;p&gt;One pitfall may arise: multiple versions of the same type package.&lt;/p&gt;

&lt;p&gt;If library A and library B depend on different versions of &lt;code&gt;@prime-types/dailymotion&lt;/code&gt;, TypeScript errors may appear.&lt;/p&gt;

&lt;p&gt;The solution is to always declare the type package as a &lt;strong&gt;peer dependency&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@prime-types/dailymotion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way, only one version can be installed at the top level, and all projects align.&lt;/p&gt;




&lt;h2&gt;
  
  
  Alternative: a single package
&lt;/h2&gt;

&lt;p&gt;For small teams, you may consider a simplified version: group all types into a single package.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trivial to set up and deploy&lt;/li&gt;
&lt;li&gt;Install one package and you're done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Harder to version types independently&lt;/li&gt;
&lt;li&gt;Package size grows quickly as you add other types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A monorepo with multiple packages is more scalable in the long term.&lt;/p&gt;




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

&lt;p&gt;&lt;code&gt;DefinitelyTyped&lt;/code&gt; is ideal for open source, but internal projects sometimes need their own solution. Centralizing TypeScript types has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removes duplication and conflicts&lt;/li&gt;
&lt;li&gt;Improves type safety across projects&lt;/li&gt;
&lt;li&gt;Gives you a single source of truth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete example is available on GitHub for you to try out! 🧑‍💻&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/prime-types" class="crayons-btn crayons-btn--primary"&gt;Discover Prime Types on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We have not discussed deployment in this article, but I will detail our deployment workflow with GitLab in a future article.&lt;/p&gt;
&lt;/blockquote&gt;




</description>
      <category>typescript</category>
      <category>types</category>
      <category>monorepo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automating merge request reviews with Danger on GitLab</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Sun, 03 Aug 2025 22:42:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/automating-merge-request-reviews-with-danger-on-gitlab-39k1</link>
      <guid>https://dev.to/yoriiis/automating-merge-request-reviews-with-danger-on-gitlab-39k1</guid>
      <description>&lt;p&gt;As developers, we spend a lot of time writing code, but also reviewing it, particularly during code review phases between teams.&lt;/p&gt;

&lt;p&gt;Sometimes, certain comments are repetitive and could be automated to save developers time. Furthermore, just like unit tests that check our code, we could add tests that check merge requests content (metadata or GIT changes).&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://danger.systems" rel="noopener noreferrer"&gt;Danger&lt;/a&gt; comes in. The same tool GitLab itself uses in its open-source projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Danger
&lt;/h2&gt;

&lt;p&gt;Danger is a tool to automate code review tasks, available on several source code management like GitHub, GitLab or BitBucket.&lt;/p&gt;

&lt;p&gt;First, you define rules in a configuration file that run in CI process. Then, when a merge request is created or updated, Danger analyzes the changes and posts comments with warnings or errors into the merge request.&lt;/p&gt;

&lt;p&gt;There are two main versions of Danger, Ruby and JavaScript, see &lt;a href="https://danger.systems/js/js-vs-ruby.html" rel="noopener noreferrer"&gt;Which Danger should I use&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At Prisma Media, we have chosen the Ruby version because it is more mature and stable, and because we wanted to draw inspiration from the configuration files used by the GitLab teams. There is no point in reinventing rules, especially if some of them work within a project as important and global as GitLab.&lt;/p&gt;

&lt;p&gt;Danger can perform several types of actions, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking changes made to a specific file (GIT changes)&lt;/li&gt;
&lt;li&gt;Checking merge request metadata (title, assignee, reviewer, label, etc.)&lt;/li&gt;
&lt;li&gt;Using the platform's API directly to extend its scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See details about &lt;a href="https://danger.systems/reference" rel="noopener noreferrer"&gt;Danger reference&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Our Danger rules
&lt;/h2&gt;

&lt;p&gt;To ease adoption, we began by adding a simple rule to every project: checking the CHANGELOG. Because updating the CHANGELOG was sometimes forgotten or incorrectly formatted. A simple, automated rule made this process more reliable and encouraged the use of Danger.&lt;/p&gt;

&lt;p&gt;Over time, we added more rules, focusing on recurring feedback that could be automated with Danger.&lt;/p&gt;

&lt;p&gt;Here are some examples of the rules we have in place.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merge request title must contain a Jira ticket&lt;/li&gt;
&lt;li&gt;Merge request title should be less than 72 characters&lt;/li&gt;
&lt;li&gt;Merge request must have assignees&lt;/li&gt;
&lt;li&gt;Merge request must have reviewers if marked as ready&lt;/li&gt;
&lt;li&gt;Filenames and directories must not contain uppercase letters or underscores&lt;/li&gt;
&lt;li&gt;Changelog must be updated and contain a link to the merge request&lt;/li&gt;
&lt;li&gt;Unit tests should be updated if &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt; files are modified&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package-lock.json&lt;/code&gt; should be updated if &lt;code&gt;package.json&lt;/code&gt; changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on your requirements, the rules can be a warning or an error if you want the job to fail. Exceptions can also be added, for example if the change is trivial or concerns a part of the code that does not require a new version, the rule is skipped.&lt;/p&gt;

&lt;p&gt;We also pay particular attention to the wording of Danger's messages to ensure they are easy to understand.&lt;/p&gt;

&lt;p&gt;In addition, if at least one comment has been created, a link allowing Danger to be replayed is displayed. This is very useful if, for example, the feedback concerns metadata from the merge request that can be corrected with just a retry on the job.&lt;/p&gt;

&lt;p&gt;At Prisma Media, we call our Danger review bot &lt;strong&gt;Alfred&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxoii6y9y8lv5ooy1jt6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxoii6y9y8lv5ooy1jt6x.png" alt="Danger review comment on a merge request"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  GitLab CI integration
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;Dangerfile&lt;/code&gt; at the root of the repository. The rule will analyze the metadata of the merge request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mr_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;
  &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="s1"&gt;'Merge request title should be less than 72 characters.'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mr_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'assignees'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
  &lt;span class="nb"&gt;fail&lt;/span&gt; &lt;span class="s1"&gt;'Merge request must have assignee(s).'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add a job &lt;code&gt;danger-review&lt;/code&gt; in the &lt;code&gt;.gitlab-ci.yaml&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;danger-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby:3.3.0&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;GIT_STRATEGY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fetch&lt;/span&gt;
    &lt;span class="na"&gt;GIT_DEPTH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;danger-review_ruby-3.3.0_danger-9.5.1_danger-gitlab-8.0.0'&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vendor/bundle&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle config set --local path 'vendor/ruby'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install danger --version 9.5.1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install danger-gitlab --version 8.0.0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;danger --verbose --fail-on-errors=true --dangerfile=Dangerfile&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 This job uses a versioned GitLab cache for Ruby gems to speed up runs; alternatively, a prebuilt Docker image can be used to avoid repeated installs.&lt;/p&gt;

&lt;p&gt;To ensure that Danger has access to your entire GIT repository history, with GitLab it is best to override the settings &lt;a href="https://docs.gitlab.com/ci/pipelines/settings/#choose-the-default-git-strategy" rel="noopener noreferrer"&gt;&lt;code&gt;GIT_STRATEGY&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.gitlab.com/ci/pipelines/settings/#limit-the-number-of-changes-fetched-during-clone" rel="noopener noreferrer"&gt;&lt;code&gt;GIT_DEPTH&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The job rules above is important to limit the execution of Danger to merge request pipelines. Because if you run it on a branch pipeline, you could end up in a scenario where the job runs even though the merge request does not yet exist, which would skip the job even though there may be feedback. See details for &lt;a href="https://docs.gitlab.com/ci/pipelines/merge_request_pipelines" rel="noopener noreferrer"&gt;merge request pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;For Danger to comment on merge requests, the pipeline needs a token to perform actions on the merge request.&lt;/p&gt;

&lt;p&gt;Create a Project Access Tokens with the API scope, Developer permission, and no expiration date. Add the token as a CI/CD project variable named &lt;code&gt;DANGER_GITLAB_API_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use CI and Danger as templates
&lt;/h2&gt;

&lt;p&gt;To simplify deployment across multiple projects, CI templates and Danger rules can be hosted in a dedicated repository and imported into consumer projects. This keeps the code centralized and lets it evolve independently.&lt;/p&gt;

&lt;p&gt;Create a repository named &lt;code&gt;danger&lt;/code&gt; with the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ci/
  danger-review.yaml
danger/
  changelog
    Dangerfile
    README.md
  metadata/
    Dangerfile
    README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Each Danger rule is stored into a dedicated directory with its own documentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Transform the &lt;code&gt;danger-review&lt;/code&gt; job above into a template CI with variables for personalization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ci/danger-review.yaml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;.danger-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby:3.3.0&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CONFIG_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dangerfile'&lt;/span&gt;
    &lt;span class="na"&gt;GIT_STRATEGY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fetch&lt;/span&gt;
    &lt;span class="na"&gt;GIT_DEPTH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;danger-review_ruby-3.3.0_danger-9.5.1_danger-gitlab-8.0.0'&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vendor/bundle&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle config set --local path 'vendor/ruby'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install danger --version 9.5.1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gem install danger-gitlab --version 8.0.0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;danger --verbose --fail-on-errors=true --dangerfile=${CONFIG_PATH}&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_PIPELINE_SOURCE == "merge_request_event"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the Danger rules for the changelog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;danger/changelog/Dangerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;added_files&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modified_files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CHANGELOG.md'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff_for_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CHANGELOG.md'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mr_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"iid"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mr_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"web_url"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;fail&lt;/span&gt; &lt;span class="s1"&gt;'Changelog entry must include the merge request link.'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nb"&gt;fail&lt;/span&gt; &lt;span class="s1"&gt;'"CHANGELOG.md" file is missing in GIT diff, version needs to be bump.'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the Danger rules for the metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;danger/metadata/Dangerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mr_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;
  &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="s1"&gt;'Merge request title should be less than 72 characters.'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mr_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'assignees'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
  &lt;span class="nb"&gt;fail&lt;/span&gt; &lt;span class="s1"&gt;'Merge request must have assignee(s).'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a consumer project, import the CI template created above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.gitlab-ci.yaml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;danger'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/ci/danger-review.yaml'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;danger-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.danger-review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the Danger rules into our root Danger file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dangerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;dangerfiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'changelog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'metadata'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;dangerfiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;import_dangerfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;gitlab: &lt;/span&gt;&lt;span class="s1"&gt;'danger'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;branch: &lt;/span&gt;&lt;span class="s1"&gt;'main'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s2"&gt;"danger/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Dangerfile"&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;blockquote&gt;
&lt;p&gt;💡 To simplify version upgrades in the &lt;code&gt;danger&lt;/code&gt; repository, use branch names like &lt;code&gt;v1&lt;/code&gt; instead of &lt;code&gt;main&lt;/code&gt;. This allows multiple versions, such as &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt;, to be maintained in parallel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each project can still create specific rules for its own needs by adding them to its local &lt;code&gt;Dangerfile&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Using Danger in CI has dramatically improved merge request quality and consistency across our projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feedback appears directly in merge request threads&lt;/li&gt;
&lt;li&gt;Rules are reusable, documented, and shared across multiple projects&lt;/li&gt;
&lt;li&gt;Teams spend less time checking trivial issues&lt;/li&gt;
&lt;li&gt;Custom project-specific rules are easy to implement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Formulating messages clearly is key. A well-written warning or error guides developers without frustration. With the retry feature and merge request pipelines, the process becomes smooth and almost mandatory.&lt;/p&gt;

&lt;p&gt;If your team hasn't tried Danger yet, it's worth exploring. You can start small, automate common rules, and expand gradually to cover project-specific needs.&lt;/p&gt;

&lt;p&gt;The complete example is available on GitHub for you to try out! 🧑‍💻&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/danger-review" class="crayons-btn crayons-btn--primary"&gt;Discover Danger Review on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://danger.systems" rel="noopener noreferrer"&gt;Danger documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/development/dangerbot" rel="noopener noreferrer"&gt;GitLab Dangerbot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://artsy.github.io/blog/2017/06/30/danger-one-oh-again" rel="noopener noreferrer"&gt;Recreate Danger in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>devops</category>
      <category>gitlab</category>
      <category>ci</category>
      <category>automation</category>
    </item>
    <item>
      <title>Rebuilding our front-end tech stack for the new teleloisirs.fr</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Sat, 03 Oct 2020 13:12:39 +0000</pubDate>
      <link>https://dev.to/yoriiis/rebuilding-our-front-end-tech-stack-for-the-new-teleloisirs-fr-442m</link>
      <guid>https://dev.to/yoriiis/rebuilding-our-front-end-tech-stack-for-the-new-teleloisirs-fr-442m</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fde9gdffwr4qxm9eu7o83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fde9gdffwr4qxm9eu7o83.png" alt="Télé-Loisirs homepage" width="800" height="1627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Télé-Loisirs is a french TV programs' website with a server-rendered in PHP Symfony. As a front-end engineer, this article covers how we did a progressive rebuild of the website with a new User Interface (UI) and a complete rewrite of the front-end stack. The main goals were to improve page load performances and to facilitate the development of new features with reusable components.&lt;/p&gt;

&lt;h3&gt;
  
  
  The progressive rebuild
&lt;/h3&gt;

&lt;p&gt;For the UI development, we started with the home page. This page is the main target for our customers and we couldn't change everything in one shot without the risk of losing users. The solution was to rebuild progressively the first page, beginning from the bottom to the top of the page.&lt;/p&gt;

&lt;p&gt;This strategy is softer for users, but incredibly more difficult to implement. Indeed, the legacy code needs to cohabit with the new code.&lt;/p&gt;

&lt;p&gt;Once all the homepage's components were rebuilt, the clean steps were very important in order to remove all the legacy code and to reorganize the files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thinking in components
&lt;/h2&gt;

&lt;p&gt;To build our Design System, all the teams worked together, the UX designers and the front-end and back-end developers. The common goal was to create reusable components all over the site. Throughout the process, we anchored our work around three technical mantras to avoid multiplication of new components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the component already exist?&lt;/li&gt;
&lt;li&gt;Can an existing component require only small updates to fit the needs?&lt;/li&gt;
&lt;li&gt;Can the design be harmonized?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these questions couldn't be answered positively, we created a new component. It is strict, but was necessary to visually harmonize all our components for our users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organize the components
&lt;/h3&gt;

&lt;p&gt;At the beginning we reviewed the mockups of the whole website to identify all the components used by each page. Then we got a list of the components to develop and use-cases.&lt;/p&gt;

&lt;p&gt;Components can be specific to a page, shared between a group of pages or shared between all pages. On our site, the groups of pages are related to the sections: program, people, news, amongst others. Each group contains multiple child pages that are free to use either global or group shared components. We added this intermediate level to avoid moving a group shared component to the global level if it wasn't required. The following tree represents our components' structure organization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The single page&lt;/span&gt;
&lt;span class="no"&gt;PAGE_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# The components of the single page&lt;/span&gt;
    &lt;span class="no"&gt;COMPONENTS&lt;/span&gt;
        &lt;span class="no"&gt;COMPONENT_NAME&lt;/span&gt;
&lt;span class="no"&gt;PAGE_GROUP&lt;/span&gt;
    &lt;span class="c1"&gt;# The child page of the group&lt;/span&gt;
    &lt;span class="no"&gt;PAGE_NAME&lt;/span&gt;
        &lt;span class="c1"&gt;# The components of the child page&lt;/span&gt;
        &lt;span class="no"&gt;COMPONENTS&lt;/span&gt;
            &lt;span class="no"&gt;COMPONENT_NAME&lt;/span&gt;
    &lt;span class="no"&gt;SHARED&lt;/span&gt;
        &lt;span class="c1"&gt;# The shared components of the group&lt;/span&gt;
        &lt;span class="no"&gt;COMPONENT_NAME&lt;/span&gt;
&lt;span class="no"&gt;SHARED&lt;/span&gt;
    &lt;span class="c1"&gt;# The global shared components&lt;/span&gt;
    &lt;span class="no"&gt;COMPONENT_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This tree structure allows us to organize all components based on where they are used on the website. It makes it easier to manage and use all components either across a page, a group of pages or even the entire website.&lt;/p&gt;
&lt;h3&gt;
  
  
  Naming the components
&lt;/h3&gt;

&lt;p&gt;For a better understanding of the different components across all teams, we decided to name all of them using a short, simple and descriptive name.&lt;/p&gt;

&lt;p&gt;Here are some of the names we use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;navigation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;related-news&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;time-navigation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;push-custom-grid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;double-broadcast-card&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Creating the design variables
&lt;/h3&gt;

&lt;p&gt;We created a file for the global CSS variables. It stores all our graphical charter elements, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Colors&lt;/li&gt;
&lt;li&gt;Gradients&lt;/li&gt;
&lt;li&gt;Shadows&lt;/li&gt;
&lt;li&gt;Transition durations&lt;/li&gt;
&lt;li&gt;Borders&lt;/li&gt;
&lt;li&gt;Fonts&lt;/li&gt;
&lt;li&gt;Media Queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That file is our reference for all the UI and is edited only sparingly to keep the UX harmonized. All the CSS files must use those variables in priority to avoid new additions.&lt;/p&gt;

&lt;p&gt;Here is an extract from our global variables file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--containerMaxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Main colors */&lt;/span&gt;
  &lt;span class="py"&gt;--realBlack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#141414&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--lightBlack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#212121&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--darkBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#696f79&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--blue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d5d9e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--grey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e5e5e5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--lightGrey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f9f9f9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff004b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--placeholderAds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e3e9f2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Dark mode */&lt;/span&gt;
  &lt;span class="py"&gt;--darkmodeRed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ff236d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--darkmodeDarkBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#070720&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--darkmodeBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1c1d42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--darkmodeRedGradient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ff236d&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ee5b35&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--darkmodePlaceholderAds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#151515&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* RGBa colors */&lt;/span&gt;
  &lt;span class="py"&gt;--blackTransparent30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--blackTransparent80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c"&gt;/* Gradients */&lt;/span&gt;
  &lt;span class="py"&gt;--redGradient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ff004b&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ee5b35&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--purpleGradient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#895de4&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#cb7995&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--blackGradient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#44474d&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#161717&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c"&gt;/* Shadows */&lt;/span&gt;
  &lt;span class="py"&gt;--purpleShadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;167&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;191&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--greyShadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c"&gt;/* Transitions */&lt;/span&gt;
  &lt;span class="py"&gt;--transition300msEase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Border-radius */&lt;/span&gt;
  &lt;span class="py"&gt;--mainBorderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Fonts */&lt;/span&gt;
  &lt;span class="py"&gt;--font-montserrat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Montserrat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Media queries */&lt;/span&gt;
&lt;span class="k"&gt;@custom-media&lt;/span&gt; &lt;span class="n"&gt;--media-mobile&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;749px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;@custom-media&lt;/span&gt; &lt;span class="n"&gt;--media-tablet&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;750px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;@custom-media&lt;/span&gt; &lt;span class="n"&gt;--media-desktop&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Static and dynamic component libraries
&lt;/h3&gt;

&lt;p&gt;In collaboration with the design team, we created two component libraries, one static and one dynamic.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;static&lt;/strong&gt; component library brings together all the components on Sketch, our design software. There, the components are static and not interactive. It allows us to easily create new pages based on the existing components. New components are automatically added to that library. It is mainly used by the design team and provides a good overview of all the currently available and designed components.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;dynamic&lt;/strong&gt; component library is created from the static library and brings together all the components in their developed version. It's a tool for the UI development, inspired from &lt;a href="https://storybook.js.org" rel="noopener noreferrer"&gt;StoryBook&lt;/a&gt;. In it, components are dynamic, interactive and use the same markup as the ones available on the website: nothing is duplicated. Static data is provided to every component so they can function independently. This library is use by all teams and provides an interactive overview of all the components available on the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2c4w76g6ow9rrh14j33i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2c4w76g6ow9rrh14j33i.png" alt="Télé-Loisirs dynamic library" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Optimizing Cumulative Layout Shift
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"I was about to click on that! Why did it move? 😭"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://web.dev/cls" rel="noopener noreferrer"&gt;Cumulative Layout Shifts&lt;/a&gt; can be disturbing for users. It happens when visible elements moves on the page because another element was added, removed or resized in that very same page. We identified the main causes of this behavior on our website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images without dimensions&lt;/li&gt;
&lt;li&gt;Advertising spaces&lt;/li&gt;
&lt;li&gt;Custom web fonts&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Placeholders for images
&lt;/h3&gt;

&lt;p&gt;During page load, images are often unavailable and the layout appears differently. Suddenly, elements jump from there to there because images are downloaded and displayed on the page. This is a normal behavior for a web page. In responsive web design, we can't fix the size of the images with the attributes &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;. The solution is to reserve the image area even if it is not yet loaded using the CSS ratio trick.&lt;/p&gt;

&lt;p&gt;Imagine the following image inside a container:&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;class=&lt;/span&gt;&lt;span class="s"&gt;"picture"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"image.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"I won't move the page, I promise"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&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;Without dimensions, the content below the image will move during the page loads. Because we know the image ratio (calculated by: &lt;code&gt;(height / width) * 100 = ratio&lt;/code&gt;), we figured we could prevent that behavior. For example, for a landscape image (16/9), the calculation is: &lt;code&gt;(1080/1920) * 100 = 56.25&lt;/code&gt;. The container's height is calculated with the padding ratio, which is responsive (excellent to handle responsive videos by the way). The image is in absolute position, outside of the page flow and fills its container. Thus, having responsive images without layout shifts is possible with this simple technique.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.picture&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;56.25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.picture&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&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;
  
  
  Placeholders for advertising
&lt;/h3&gt;

&lt;p&gt;We use several formats for advertising which can often result in multiple layout changes on the site. In order to keep fixed advertising areas, we use the previously detailed solution which results in displaying placeholders to reserve areas on the page for the advertising elements. This result in an improved UX. Indeed, textual content and small iconographies can be added to help users easily identify advertising spaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F35umz6kjcs5uj41dx9v4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F35umz6kjcs5uj41dx9v4.png" alt="Advertising placeholders on Télé-Loisirs" width="800" height="1627"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Custom web font loading
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;When a browser requests a font asset from a web server, any elements with styles invoking that font is hidden until the font asset has downloaded. This is known as the “Flash of Invisible Text,” or FOIT.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To avoid these flashes, we use the &lt;code&gt;font-display&lt;/code&gt; CSS property. The &lt;code&gt;swap&lt;/code&gt; value allows to use the next available system typeface in the font stack for text rendering until the custom font loads. Multiple values are available for different needs.&lt;/p&gt;

&lt;p&gt;More information about &lt;a href="https://css-tricks.com/font-display-masses" rel="noopener noreferrer"&gt;font-display on CSS Tricks&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Improving web performances
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Lighten dependencies
&lt;/h3&gt;

&lt;p&gt;Like most websites, we had a lot of JavaScript dependencies, but now, with all the new ECMAScript features, the game has changed. Native JavaScript is powerful and can easily replace libraries like jQuery.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's not about reinventing the wheel, but use Javascript native the most possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Javascript APIs are used in priority when they are natively supported by the browser. For older browsers, newer features are supported thanks to &lt;a href="https://polyfill.io" rel="noopener noreferrer"&gt;Polyfill.io&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Loading scripts
&lt;/h3&gt;

&lt;p&gt;Scripts can have a negative impact on the loading time of a page. The location of script tags is important. We used native script tags in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document with the &lt;code&gt;defer&lt;/code&gt; attribute. That way, scripts will be fetched as soon as possible, but the browser will wait for the DOM tree to be complete before executing them.&lt;/p&gt;

&lt;p&gt;More information about scripts loading in the article &lt;a href="https://flaviocopes.com/javascript-async-defer" rel="noopener noreferrer"&gt;Efficiently load JavaScript with defer and async&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Content hash
&lt;/h3&gt;

&lt;p&gt;To reduce the number of downloaded files by the browser on each page load, we use the webpack &lt;code&gt;[contenthash]&lt;/code&gt; option. Webpack adds a unique hash based on the content of the asset. When the asset's content changes, the &lt;code&gt;[contenthash]&lt;/code&gt; changes as well. Files compiled by webpack can remain cached until their content changes. We configured a cache of 1 year for images, JavaScript and CSS files. That way, no matter if another build is deployed to production, the hashes will remain the same for unchanged files and be updated only for edited files. In other words, the first time a user loads the page, all assets will be downloaded and cached by the browser. On the second page load, all unmodified assets will come from the browser's cache even if a new deployment occurred in between both page loads and all modified assets will be downloaded again.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code-splitting and JavaScript modules
&lt;/h3&gt;

&lt;p&gt;Code size is one of the biggest concerns for an application. Websites often combine all of their JavaScript into a single bundle. When JavaScript is served that way, the page load takes more time because it loads code that is not necessary for the current page. To serve the minimum JavaScript and CSS to the client, we split common JavaScript and CSS code.&lt;/p&gt;

&lt;p&gt;In addition, to reduce code size for modern browsers, we introduced Javascript modules using the following approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serve JavaScript modules with ES2015+ syntax for modern browsers (without Babel transpilation).&lt;/li&gt;
&lt;li&gt;Serve JavaScript with ES5 syntax for older browsers (with Babel transpilation).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've detailled a complete codelab on a dedicated article&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" class="crayons-story__hidden-navigation-link"&gt;Granular chunks and JavaScript modules for faster page loads&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/yoriiis" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" alt="yoriiis profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/yoriiis" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Yoriiis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Yoriiis
                
              
              &lt;div id="story-author-preview-content-329158" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/yoriiis" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360690%2Ff2a06ade-26ab-4c55-9a57-67512b2c7275.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Yoriiis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 10 '20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" id="article-link-329158"&gt;
          Granular chunks and JavaScript modules for faster page loads
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webperf"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webperf&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webpack"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webpack&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  Delivering only what the user needs, when he needs it
&lt;/h3&gt;

&lt;p&gt;Today, websites have rich interfaces and display a lot of content. But users still don't see content outside the first screen. So, why would they need to load content they don't see yet? Lazy-loading scripts and contents on scroll can be highly beneficial for performance. We load Javascript bundles that contains only what the user needs and ideally, only what he can see on his screen.&lt;/p&gt;

&lt;p&gt;We use the Web API &lt;code&gt;IntersectionObserver&lt;/code&gt; to watch when a user is near a target element. For example, all the content below the first screen, which has Javascript dependencies, can be instanciated later. The &lt;code&gt;rootMargin&lt;/code&gt; parameter allows us to specify when exactly to trigger elements, according to the user's need. We use a margin of 1 dynamic screen height to trigger the lazy-load like the following example:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Function is triggered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0px 0px &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px 0px`&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;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#footer&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;When the observer detects the target element, a dynamic import is triggered, all assets are loaded and the related JavaScript code is executed.&lt;/p&gt;

&lt;p&gt;For browser support, we use the &lt;a href="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver" rel="noopener noreferrer"&gt;Polyfill.io IntersectionObserver&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An open-source version of our observer module is available: &lt;a href="https://github.com/yoriiis/lazy-observer" rel="noopener noreferrer"&gt;lazy-observer on Github&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SVG sprites
&lt;/h3&gt;

&lt;p&gt;We use SVG files for icons to avoid HTTP requests and for their flexibility. Additionally, these icons are perfectly displayed no matter the pixel ratio; and animations can be done using CSS. To prevent icons from flickering during the page load, we use SVG sprites. Their content are inlined directly into the HTML. We are using the &lt;a href="https://github.com/yoriiis/svg-chunk-webpack-plugin" rel="noopener noreferrer"&gt;svg-chunk-webpack-plugin&lt;/a&gt; to automate the process of generating each sprites. Each page only imports its own svg sprite which has previously been optimized using &lt;a href="https://github.com/svg/svgo" rel="noopener noreferrer"&gt;svgo&lt;/a&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Responsive images
&lt;/h3&gt;

&lt;p&gt;Users' screens are all different by sizes (watches, phones, tablets, laptops, desktops) and by pixel density (1x, 2x, 3x). Pictures made for a 1x pixel density can appear pixelated on devices with higher pixel density. Nomad devices generally have a slower connection. To provide the most suitable image format for any user, we need responsive images.&lt;/p&gt;

&lt;p&gt;For images with the same ratio for all different breakpoints, the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag, along with the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; attributes are enough. For more complex use cases, the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; tag can be used, but it has the disadvantage of increasing the DOM size.&lt;/p&gt;

&lt;p&gt;The following example displays an image that is compatible with different screen sizes, all formats (mobile, tablet and desktop), plus, the 1x and 2x pixel density.&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;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"image-64x90.jpg"&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 750px) 64px, (max-width: 1023px) 64px, (min-width: 1024px) 64px"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"image-64x90.jpg 64w, image-128x180.jpg 128w"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"My responsive image"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Better accessibility
&lt;/h3&gt;

&lt;p&gt;Accessibility is essential on websites. It provides a better experience for all users. We use tools like Google Lighthouse to generate analysis reports, they contain useful information to improve it.&lt;/p&gt;

&lt;p&gt;Some rules can greatly improve the score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a minimum size for all links and buttons, along the &lt;code&gt;padding&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;h1|2|3|4|5|6&amp;gt;&lt;/code&gt; for titles&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; for lists&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; for link and the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; for elements with Javascript action&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;alt&lt;/code&gt; attribute on images&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;title&lt;/code&gt; attribute on links&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;aria-label&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;button|a&amp;gt;&lt;/code&gt; without text&lt;/li&gt;
&lt;li&gt;Adjust the contrast for the color of the design&lt;/li&gt;
&lt;li&gt;Respect HTML descendants (&lt;code&gt;ul&amp;gt;li&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/yoriiis/4c1a47b13003346810de0b559cd6d45e#file-css-tips-css" rel="noopener noreferrer"&gt;CSS tips for semantic&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;To monitor performance, we use two different tools:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://speedcurve.com" rel="noopener noreferrer"&gt;SpeedCurve&lt;/a&gt; to analyze daily several of our main pages. This allows us to detect different types of issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page load duration&lt;/li&gt;
&lt;li&gt;Page size&lt;/li&gt;
&lt;li&gt;Assets size&lt;/li&gt;
&lt;li&gt;Number of requests&lt;/li&gt;
&lt;li&gt;Lighthouse score&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/speed/pagespeed/insights" rel="noopener noreferrer"&gt;Google Page Speed Insights&lt;/a&gt; for occasional reports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk2efyomyjc5ipmjz4015.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk2efyomyjc5ipmjz4015.jpg" alt="Google Page Speed Insights report" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Rethinking CSS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CSS naming conventions
&lt;/h3&gt;

&lt;p&gt;To improve maintainability and performance, we use a CSS naming convention: the flat hierarchy of selectors, inspired from BEM (Block Element Modifier) and &lt;a href="http://benfrain.com/enduring-css-writing-style-sheets-rapidly-changing-long-lived-projects/#l7" rel="noopener noreferrer"&gt;FUN&lt;/a&gt;. CSS selectors are unique and shorter, which leads to smaller CSS files. To avoid class names that become too long quickly, we keep &lt;code&gt;modifier&lt;/code&gt; classes independent from the block class name. In addition, the block class name and the element class name use the camelCase syntax with an hyphen as separator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.blockName-elementName&lt;/span&gt; &lt;span class="nc"&gt;.modifier&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;
  
  
  CSS variables for theming (dark mode)
&lt;/h3&gt;

&lt;p&gt;The dark mode applies a custom theme to the website. It works by adding a class name to the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element. Our first approach was to override existing styles with rules that had higher specificities. We quickly noticed that this approach had issues. One of them being a considerable increase of the size of CSS files.&lt;/p&gt;

&lt;p&gt;We've switched to native &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" rel="noopener noreferrer"&gt;CSS variables&lt;/a&gt; for theming. That way the size of our CSS files remains lighter even if they contain both light and dark mode styles. Below, an example of the background color of an header element being overridden by another color when the dark mode is enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.darkMode&lt;/span&gt; &lt;span class="nc"&gt;.header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1c1d42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#212121&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--backgroundColor&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;blockquote&gt;
&lt;p&gt;Support for IE11 had to be dropped to use native CSS variables and keep small CSS files. Otherwise the generated file would have duplicated all the CSS rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyaaqtr5cd06mxyeu27ve.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyaaqtr5cd06mxyeu27ve.png" alt="Télé-Loisirs dark mode" width="800" height="1627"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Code review
&lt;/h2&gt;

&lt;p&gt;Team work are important part of our development processes. Code reviews helps improving the maintainability of a project, team spirit and allow everyone to increase theirs skills. To make the code review easier, merge requests needs to be small and their context needs to be respected. Merge requests are kept small which leads to a more efficient review.&lt;/p&gt;

&lt;p&gt;Some of the rules we follow are listed in an article (in French) by &lt;a href="https://medium.com/just-tech-it-now/tu-peux-valider-ma-pullrequest-8a11b16f20e6" rel="noopener noreferrer"&gt;@Julien Hatzig&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Promote asynchronous reviews.&lt;/li&gt;
&lt;li&gt;Avoid making the review synchronous by asking for validation, but rather ask for a review of your work. This will put the reviewer in a better position which will leads to constructive feedback.&lt;/li&gt;
&lt;li&gt;Add a context with a description in the header of the merge request. The reviewer doesn't know the topic you worked on.&lt;/li&gt;
&lt;li&gt;Take the time to review other people's code.&lt;/li&gt;
&lt;li&gt;Be benevolent in exchanges, favor the conditional in sentences, suggest solutions, describe problems.&lt;/li&gt;
&lt;li&gt;Avoid sniper reviews.&lt;/li&gt;
&lt;li&gt;Comments are not negative for the developer, they stimulate discussion and lead to improvements. Discuss more when necessary to find the most suitable solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Additional reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.dareboost.com/fr/2020/09/cumulative-layout-shift-stabilite-page" rel="noopener noreferrer"&gt;Cumulative Layout Shift&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>webperf</category>
      <category>webpack</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Granular chunks and JavaScript modules for faster page loads</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Sun, 10 May 2020 09:37:07 +0000</pubDate>
      <link>https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9</link>
      <guid>https://dev.to/yoriiis/granular-chunks-and-javascript-modules-for-faster-page-loads-4pd9</guid>
      <description>&lt;p&gt;The race to performance increases from year to year and the front-end ecosystem is evolving more than never.&lt;/p&gt;

&lt;p&gt;This article covers how to build a Webpack configuration to improve page load performance. Learn how to set up a granular chunking strategy to split common code. Then, serve modern code with JavaScript modules to modern browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webpack configuration
&lt;/h2&gt;

&lt;p&gt;To start, the configuration has the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple Page Application&lt;/li&gt;
&lt;li&gt;Development and production environment&lt;/li&gt;
&lt;li&gt;JavaScript transpilation with Babel and &lt;code&gt;preset-env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CSS extraction&lt;/li&gt;
&lt;li&gt;Default optimization behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, let's write our Webpack starter configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;webpack.config.js&lt;/strong&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&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;MiniCssExtractPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mini-css-extract-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Export a function for environment flexibility&lt;/span&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Webpack mode from the npm script&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProduction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Object entry for Multiple Page Application&lt;/span&gt;
    &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;news&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;news.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// Babel transpilation for JavaScript files&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-env&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;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;last 2 versions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                  &lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="c1"&gt;// Include polyfills from core-js package&lt;/span&gt;
                  &lt;span class="na"&gt;useBuiltIns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;usage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;corejs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="c1"&gt;// Extract content for CSS files&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;css$/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MiniCssExtractPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.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="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;// Configure CSS extraction&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MiniCssExtractPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFilename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].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="c1"&gt;// Default optimization behavior depending on environment&lt;/span&gt;
    &lt;span class="na"&gt;optimization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;minimize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isProduction&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;For more flexibility, the configuration exports a function, but other &lt;a href="https://webpack.js.org/configuration/configuration-types/#exporting-a-function" rel="noopener noreferrer"&gt;configuration types&lt;/a&gt; are available.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;entry&lt;/code&gt; key is an object to accept multiple entries (Multiple Page Application). Each entry contains the code for a specific page of the site (ex: home, news, etc.).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;module.rules&lt;/code&gt; key is an array with two rules, one for the JavaScript files and one for the CSS files.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;babel-loader&lt;/code&gt; is used to transpile JavaScript with the presets from &lt;code&gt;@babel/preset-env&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Browsers list&lt;br&gt;
The &lt;code&gt;last 2 versions&lt;/code&gt; transpiles the code for the last two versions of every browsers. It's not the best option, choose instead a browsers list to match your real audience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;css-loader&lt;/code&gt; is used to interpret CSS files and &lt;code&gt;MiniCssExtractPlugin&lt;/code&gt; to extract CSS content in a dedicated file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;plugins&lt;/code&gt; array has a unique plugin &lt;code&gt;MiniCssExtractPlugin&lt;/code&gt; to extract CSS content.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;optimization&lt;/code&gt; object has the default behavior; the &lt;code&gt;minimize&lt;/code&gt; option depends of the Webpack &lt;code&gt;mode&lt;/code&gt; (development or production).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Minimizer&lt;br&gt;
The &lt;code&gt;minimize&lt;/code&gt; key can be replaced by &lt;code&gt;minimizer&lt;/code&gt; to perform optimization with &lt;a href="https://webpack.js.org/plugins/terser-webpack-plugin" rel="noopener noreferrer"&gt;TerserPlugin&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's add the npm scripts that will start and build Webpack:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --mode=development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --mode=production"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Granular chunks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Split common code
&lt;/h3&gt;

&lt;p&gt;Webpack &lt;code&gt;splitChunks&lt;/code&gt; allows to split common code used inside all entrypoints.&lt;/p&gt;

&lt;p&gt;This generates one entrypoint file for JavaScript and CSS plus multiple chunk files which contain common code.&lt;/p&gt;

&lt;p&gt;Imagine the pages share some common code for the header. Without the optimization, common code is duplicated across every entrypoints.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkivf1zj3o9k7d7skd90p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkivf1zj3o9k7d7skd90p.jpg" alt="Webpack — SplitChunks disabled" width="800" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the optimization, a chunk is automatically created with the shared code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyjv86h5f1gy1b8eax2m3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyjv86h5f1gy1b8eax2m3.jpg" alt="Webpack — SplitChunks enabled" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use this option with multiple entrypoints, the easiest is to install the &lt;code&gt;chunks-webpack-plugin&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;chunks-webpack-plugin &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, update the Webpack configuration to add the plugin.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ChunksWebpackPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chunks-webpack-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChunksWebpackPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/[name]-[type].html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;templateStyle&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;link rel="stylesheet" href="&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;" /&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;templateScript&lt;/span&gt;&lt;span class="p"&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="nx"&gt;entryName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;script defer src="&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;"&amp;gt;&amp;lt;/script&amp;gt;`&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the &lt;code&gt;optimization.splitChunks&lt;/code&gt; to target &lt;code&gt;all&lt;/code&gt; type of chunks.&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;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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="na"&gt;optimization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;splitChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Build performance&lt;br&gt;
Enable &lt;code&gt;splitChunks&lt;/code&gt; only for production. I strongly advise you to enable the &lt;code&gt;name&lt;/code&gt; option for debugging.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's all, granular chunking is done, no more configuration 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Include chunk templates
&lt;/h3&gt;

&lt;p&gt;Now that everything is set up, include the generated templates in the page templates.&lt;/p&gt;

&lt;p&gt;With a multiple page application, a base layout is commonly used and pages override blocks. The layout defines the blocks. The pages includes specific files inside these blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;base.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;styles&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;scripts&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
      &lt;span class="c"&gt;{# Application code here #}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;home.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;styles&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/home-styles.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;scripts&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/home-script.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;news.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;styles&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/news-styles.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;scripts&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/news-script.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Content of these generated templates will looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;home-styles.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"dist/vendors~home~news.css"&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;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"dist/home.css"&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;strong&gt;home-scripts.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/vendors~home~news.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/home.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Script type module &amp;amp; nomodule
&lt;/h2&gt;

&lt;p&gt;Many polyfills are not needed for modern browsers. By using modules, Babel transpilation can be avoided and bundle sizes are reduced.&lt;/p&gt;

&lt;p&gt;HTML provides useful attributes for the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to detect modern browsers and JavaScript modules' support.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;script type="module"&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Serve JavaScript modules with ES2015+ syntax for modern browsers (without Babel transpilation).&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;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/modern/home.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;script nomodule&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Serve JavaScript with ES5 syntax for older browsers (with Babel transpilation).&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;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dist/legacy/home.js"&lt;/span&gt; &lt;span class="na"&gt;nomodule&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Browsers support
&lt;/h3&gt;

&lt;p&gt;Browsers that support modules ignore scripts with the &lt;code&gt;nomodule&lt;/code&gt; attribute. And vice versa, browsers that do not support modules ignore scripts with the &lt;code&gt;type="module"&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;This feature is supported by all latest versions of modern browsers, see on &lt;a href="https://caniuse.com/#feat=es6-module" rel="noopener noreferrer"&gt;Can I use&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Bad guys&lt;br&gt;
IE 11 and Safari 10 are a problematic and download both bundles. Discussion is in progress to fix it on Safari, see the &lt;a href="https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc" rel="noopener noreferrer"&gt;Github Gist&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Multiple Webpack configurations
&lt;/h3&gt;

&lt;p&gt;Instead of exporting a single Webpack configuration, you may export multiple configurations. Simply wrap the different object configurations inside an array.&lt;/p&gt;

&lt;p&gt;Let's create a function to avoid code duplication between our configurations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;config-generator.js&lt;/strong&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&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;MiniCssExtractPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mini-css-extract-plugin&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;ChunksWebpackPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chunks-webpack-plugin&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;configGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;presets&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Custom attribute depending the browsers&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scriptAttribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browsers&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modern&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type="module"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nomodule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The name of the configuration&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;news&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;news.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`./dist/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Presets depending the browsers&lt;/span&gt;
            &lt;span class="nx"&gt;presets&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;css$/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MiniCssExtractPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.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="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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MiniCssExtractPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFilename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChunksWebpackPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/[name]-[type].html.twig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;templateStyle&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;link rel="stylesheet" href="&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;" /&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;templateScript&lt;/span&gt;&lt;span class="p"&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="nx"&gt;entryName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modern&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;script type="module" src="&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;"&amp;gt;&amp;lt;/script&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;script nomodule defer src="&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;"&amp;gt;&amp;lt;/script&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;optimization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;splitChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the &lt;code&gt;webpack.config.js&lt;/code&gt; needs to export two configuration with the &lt;code&gt;configGenerator&lt;/code&gt; function. The first for modern browsers and the second for legacy browsers, with the different Babel presets. The presets target &lt;a href="https://babeljs.io/docs/en/babel-preset-env#targetsesmodules" rel="noopener noreferrer"&gt;&lt;code&gt;esmodules&lt;/code&gt;&lt;/a&gt; browsers instead of a browsers list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;webpack.config.js&lt;/strong&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="nx"&gt;configGenerator&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;./config-generator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProduction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Modern browsers that support Javascript modules&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configModern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;configGenerator&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modern&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-env&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;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;esmodules&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;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;// Legacy browsers that do not support Javascript modules&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configLegacy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;configGenerator&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;legacy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-env&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;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;esmodules&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;useBuiltIns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;usage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;corejs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;configModern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;configLegacy&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;When running Webpack, all configurations are built.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Run a specific configuration&lt;br&gt;
Add &lt;code&gt;--config-name=&amp;lt;BROWSERS&amp;gt;&lt;/code&gt; at the end of the npm script. Replace &lt;code&gt;&amp;lt;BROWSERS&amp;gt;&lt;/code&gt; by the name of the Webpack configuration (&lt;code&gt;modern&lt;/code&gt; or &lt;code&gt;legacy&lt;/code&gt; in the example above).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Update chunk templates
&lt;/h3&gt;

&lt;p&gt;Include both bundles for JavaScript to target modern and legacy browsers. For CSS, the configuration is identical for both browsers, you can import one or the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;home.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;styles&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/modern/home-styles.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;scripts&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/modern/home-script.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/legacy/home-script.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;news.html.twig&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s1"&gt;'base.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;styles&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/modern/news-styles.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;scripts&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/modern/news-script.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'dist/templates/legacy/news-script.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You now understand how to customize the Webpack configuration to improve page load performance.&lt;/p&gt;

&lt;p&gt;Granular chunks with Webpack and &lt;code&gt;chunks-webpack-plugin&lt;/code&gt; offer a better strategy to shares common code.&lt;/p&gt;

&lt;p&gt;Next, JavaScript modules provide minimal polyfills and smaller bundles for modern browsers.&lt;/p&gt;

&lt;p&gt;The complete example is available on GitHub for you to try out! 🧑‍💻&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yoriiis/dev.to/tree/main/projects/webpack-splitchunks-esmodule" class="crayons-btn crayons-btn--primary"&gt;Discover Webpack splitChunks ESModule on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/granular-chunking-nextjs" rel="noopener noreferrer"&gt;Improved Next.js and Gatsby page load performance with granular chunking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/codelab-serve-modern-code" rel="noopener noreferrer"&gt;Serve modern code to modern browsers for faster page loads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jamie.build/last-2-versions" rel="noopener noreferrer"&gt;last 2 versions" considered harmful&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@Yoriiis/the-real-power-of-webpack-4-splitchunks-plugin-fad097c45ba0" rel="noopener noreferrer"&gt;The real power of Webpack 4 SplitChunks Plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Photo by @dylan_nolte on Unsplash&lt;/em&gt;&lt;br&gt;
&lt;em&gt;With thanks to Emilie Gervais for her review&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webperf</category>
      <category>webpack</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The real power of Webpack 4 SplitChunks Plugin</title>
      <dc:creator>Yoriiis</dc:creator>
      <pubDate>Thu, 01 Aug 2019 21:26:00 +0000</pubDate>
      <link>https://dev.to/yoriiis/the-real-power-of-webpack-4-splitchunks-plugin-38p2</link>
      <guid>https://dev.to/yoriiis/the-real-power-of-webpack-4-splitchunks-plugin-38p2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published on &lt;a href="https://medium.com/prisma-media/the-real-power-of-webpack-4-splitchunks-plugin-fad097c45ba0" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; and has been slightly updated for Dev.to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa3mk6xz8mf3qi2p51mwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa3mk6xz8mf3qi2p51mwe.png" alt="Webpack — Code Splitting" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have read Hemal Patel's excellent article about the &lt;a href="https://medium.com/dailyjs/webpack-4-splitchunks-plugin-d9fbbe091fd0" rel="noopener noreferrer"&gt;Mysterious SplitChunks Plugin&lt;/a&gt;, you probably asked yourself the same question as I did.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I include the code splitting optimization &lt;code&gt;splitChunks.chunks&lt;/code&gt; "all", into my project?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SplitChunks finds modules which are shared between chunks and splits them into separate chunks to &lt;strong&gt;reduce duplication&lt;/strong&gt; or &lt;strong&gt;separate vendor&lt;/strong&gt; modules from application modules.&lt;/p&gt;

&lt;p&gt;SplitChunks accepts 3 possible values &lt;code&gt;initial&lt;/code&gt;, &lt;code&gt;async&lt;/code&gt; or &lt;code&gt;all&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;According to the Webpack documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Providing &lt;code&gt;all&lt;/code&gt; can be particularly powerful, because it means that chunks can be shared even between async and non-async chunks"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hemal Patel explains it differently:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, webpack! I don't care if it is a dynamically imported module or non-dynamically imported module. Apply optimization over all of them. But make sure that… naah, you are smart enough to do that!"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The difference between Entry Points and Chunks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Entry Points
&lt;/h3&gt;

&lt;p&gt;The point(s) to enter the application. Here the application starts executing. If an array is passed all items will be executed.&lt;/p&gt;

&lt;p&gt;A dynamically loaded module is not an entry point.&lt;/p&gt;

&lt;p&gt;Simple rule: one entry point per HTML page. SPA: one entry point. MPA: multiple entry points (this, according to the Webpack documentation).&lt;/p&gt;

&lt;p&gt;Example of entry points in a multiple page application&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;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="c1"&gt;//...&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./home.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./about.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./contact.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chunks
&lt;/h3&gt;

&lt;p&gt;"Chunk" is a Webpack specific term that is used internally to manage the bundling process. Webpack composes bundles out of chunks. Chunks are automatically generated by Webpack from a list of entry points.&lt;/p&gt;




&lt;h2&gt;
  
  
  Webpack configuration
&lt;/h2&gt;

&lt;p&gt;When you include this optimization into your Webpack configuration, it must look like this:&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;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="c1"&gt;//...&lt;/span&gt;
  &lt;span class="na"&gt;optimization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;splitChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;name&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="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;name&lt;/code&gt; parameter corresponds to the name of the split chunks. Providing &lt;code&gt;true&lt;/code&gt; will automatically generate a name based on chunks and cache group key. I strongly advise you to &lt;strong&gt;enable this option for debugging&lt;/strong&gt; during &lt;strong&gt;development only&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, Webpack will automatically analyze shared code and dependencies between your files (CSS and Javascript) and will generate new chunks associated with these entry points.&lt;/p&gt;

&lt;p&gt;The question is now:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the link between an entry point and its corresponding chunk(s)?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The plugin &lt;a href="https://webpack.js.org/plugins/html-webpack-plugin" rel="noopener noreferrer"&gt;HtmlWebpackPlugin&lt;/a&gt; generates HTML files with entry points, but without chunks. Also, this plugin is not appropriate for a multiple page application, it requires an instantiation per entry point and this is not optimal for build performance.&lt;/p&gt;

&lt;p&gt;After days of searching the Internet for the best solution to integrate &lt;strong&gt;code splitting&lt;/strong&gt; optimization into my project, I finally decided to code my own plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install ChunksWebpackPlugin
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/yoriiis/chunks-webpack-plugin" rel="noopener noreferrer"&gt;chunks-webpack-plugin&lt;/a&gt; creates HTML files to serve your Webpack bundles. It is very convenient for &lt;strong&gt;multiple entry points&lt;/strong&gt; and works &lt;strong&gt;without configuration&lt;/strong&gt; for basic usage. For more complex usage, there are hooks available for more flexibility.&lt;/p&gt;

&lt;p&gt;All the documentation is available on Github and npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; chunks-webpack-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Includes assets in your templates
&lt;/h2&gt;

&lt;p&gt;Include each HTML file on the corresponding page like the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'home-styles.html'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'home-scripts.html'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You are free to change many of the options including the path, the name, the language and the content of the output files.&lt;/strong&gt; This example is in PHP, but it works with TWIG and all others languages.&lt;/p&gt;

&lt;p&gt;Generated HTML files contain script and style tags with the ordered list of all chunks associated with the entry point plus the entry point itself.&lt;/p&gt;

&lt;p&gt;Example of the content of the file &lt;code&gt;home-styles.html&lt;/code&gt;:&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;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".styles/0.cfcccdf4f79b40e.css"&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;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".styles/home.cfcccdf4f79b40e.css"&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;Example of the content of the file &lt;code&gt;home-scripts.html&lt;/code&gt;:&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;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./scripts/1.cfcccdf4f79b40e.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./scripts/0.cfcccdf4f79b40e.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./scripts/home.cfcccdf4f79b40e.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Debug with WebpackBundleAnalyzer
&lt;/h2&gt;

&lt;p&gt;Use the package &lt;a href="https://www.npmjs.com/package/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;WebpackBundleAnalyzer&lt;/a&gt; to understand how your code is being split.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fhe4hnutbvfcafmkre97j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fhe4hnutbvfcafmkre97j.png" alt="Webpack Bundle Analyzer report" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  In conclusion,
&lt;/h2&gt;

&lt;p&gt;I have integrated code splitting with the plugin &lt;a href="https://github.com/yoriiis/chunks-webpack-plugin" rel="noopener noreferrer"&gt;chunks-webpack-plugin&lt;/a&gt; into several projects, including the website &lt;a href="https://www.programme-tv.net" rel="noopener noreferrer"&gt;Télé-Loisirs&lt;/a&gt;, and I am very pleased with the result!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Many thanks to &lt;a href="https://webpack.js.org/concepts" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt; team for the awesome documentation and to various discussions and blog posts from the community, I could not have done this without it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>performance</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
