<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>2HAAS Blog</title>
<link>https://2haas.com/blog/</link>
<atom:link href="https://2haas.com/blog/feed.xml" rel="self" type="application/rss+xml"/>
<description>Practical web development tips, tool reviews, code snippets and industry news from the 2HAAS engineering team. One idea per post, explained plainly.</description>
<language>en</language>
<lastBuildDate>Thu, 22 Oct 2026 20:12:03 GMT</lastBuildDate>
<item>
<title>Improve Web UI Responsiveness with Web Workers</title>
<link>https://2haas.com/blog/improve-web-ui-responsiveness-workers/</link>
<guid isPermaLink="true">https://2haas.com/blog/improve-web-ui-responsiveness-workers/</guid>
<pubDate>Thu, 22 Oct 2026 20:12:03 GMT</pubDate>
<description>Learn how to use Web Workers to offload heavy computations from the main thread, ensuring your web application remains responsive and smooth.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>Heavy computations can freeze your web page, making it unresponsive and frustrating for users. When the main JavaScript thread is busy, the browser cannot update the user interface, process user input, or run animations. This leads to a janky experience where your application feels slow or even broken.</p>
<h2 id="understanding-the-javascript-main-thread">Understanding the JavaScript Main Thread</h2>
<p>Browsers execute JavaScript in a single thread by default, known as the main thread. This thread is responsible for everything visible and interactive on a web page: rendering HTML, applying CSS, running animations, processing user input events, and executing all your JavaScript code. If a complex or long-running script blocks this thread, the browser's UI effectively freezes. This is problematic for data processing, image manipulation, or intensive calculations.</p>
<h2 id="how-web-workers-keep-your-ui-smooth">How Web Workers Keep Your UI Smooth</h2>
<p>Web Workers provide a way to run scripts in background threads, separate from the main execution thread of the web application. They are perfect for tasks that consume a lot of CPU cycles without blocking the user interface. Workers cannot directly access the DOM, but they can communicate with the main thread by sending and receiving messages. This asynchronous communication pattern ensures that heavy tasks do not interfere with UI responsiveness.</p>
<p>Here is a basic workflow for using Web Workers:</p>
<ol>
<li>
<p><strong>Create a Worker:</strong> Instantiate a new <code>Worker</code> object in your main script, pointing to a separate JavaScript file that contains the worker's code.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// main.js</span>
<span class="hljs-keyword">const</span> myWorker = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Worker</span>(<span class="hljs-string">'worker.js'</span>);
</code></pre>
</li>
<li>
<p><strong>Send Data to the Worker:</strong> Use the <code>postMessage()</code> method to send data from the main thread to the worker. Data is copied, not shared, between threads.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// main.js</span>
myWorker.<span class="hljs-title function_">postMessage</span>({ <span class="hljs-attr">type</span>: <span class="hljs-string">'startComputation'</span>, <span class="hljs-attr">data</span>: [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>] });
</code></pre>
</li>
<li>
<p><strong>Receive Data in the Worker:</strong> Inside <code>worker.js</code>, set up an <code>onmessage</code> event listener to receive messages from the main thread.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// worker.js</span>
self.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) {
    <span class="hljs-keyword">if</span> (e.<span class="hljs-property">data</span>.<span class="hljs-property">type</span> === <span class="hljs-string">'startComputation'</span>) {
        <span class="hljs-keyword">const</span> result = <span class="hljs-title function_">performHeavyCalculation</span>(e.<span class="hljs-property">data</span>.<span class="hljs-property">data</span>);
        self.<span class="hljs-title function_">postMessage</span>({ <span class="hljs-attr">type</span>: <span class="hljs-string">'computationComplete'</span>, <span class="hljs-attr">result</span>: result });
    }
};

<span class="hljs-keyword">function</span> <span class="hljs-title function_">performHeavyCalculation</span>(<span class="hljs-params">numbers</span>) {
    <span class="hljs-comment">// Simulate a long-running task</span>
    <span class="hljs-keyword">let</span> sum = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &#x3C; <span class="hljs-number">1000000000</span>; i++) { <span class="hljs-comment">// A very long loop</span>
        sum += <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">sqrt</span>(i);
    }
    <span class="hljs-keyword">return</span> sum + numbers.<span class="hljs-title function_">reduce</span>(<span class="hljs-function">(<span class="hljs-params">acc, num</span>) =></span> acc + num, <span class="hljs-number">0</span>);
}
</code></pre>
</li>
<li>
<p><strong>Receive Data from the Worker:</strong> Back in <code>main.js</code>, add another <code>onmessage</code> listener to get the results back from the worker.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// main.js</span>
myWorker.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) {
    <span class="hljs-keyword">if</span> (e.<span class="hljs-property">data</span>.<span class="hljs-property">type</span> === <span class="hljs-string">'computationComplete'</span>) {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Result from worker:'</span>, e.<span class="hljs-property">data</span>.<span class="hljs-property">result</span>);
        <span class="hljs-comment">// Update UI with result</span>
    }
};
</code></pre>
</li>
<li>
<p><strong>Terminate the Worker (Optional):</strong> Once the worker's task is done and you no longer need it, you can terminate it to free up resources.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// main.js</span>
myWorker.<span class="hljs-title function_">terminate</span>();
</code></pre>
</li>
</ol>
<h2 id="types-of-web-workers">Types of Web Workers</h2>
<p>There are a few specialized types of Web Workers beyond the basic dedicated worker:</p>
<ul>
<li><strong>Dedicated Workers:</strong> The standard worker type, associated with a single script that created it.</li>
<li><strong>Shared Workers:</strong> Can be accessed by multiple scripts, even from different windows or iframes, as long as they are from the same origin. Communication happens via a <code>Port</code> object.</li>
<li><strong>Service Workers:</strong> Act as a proxy between the browser and the network. They enable features like offline experiences, push notifications, and asset caching, operating in their own lifecycle.</li>
<li><strong>Audio Workers / Worklets:</strong> More specialized APIs for low-latency audio processing or custom rendering, often used in conjunction with the Web Audio API or Canvas API.</li>
</ul>
<h2 id="common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</h2>
<p>While powerful, Web Workers come with some limitations and considerations:</p>
<ul>
<li><strong>DOM Access:</strong> Workers cannot directly access the Document Object Model (DOM). All UI updates must be performed on the main thread after receiving results from the worker.</li>
<li><strong>Global Variables:</strong> Workers have their own global scope (<code>self</code>). They cannot access variables from the main thread directly, nor can the main thread access worker variables. All data exchange is via <code>postMessage()</code>.</li>
<li><strong>Data Transfer:</strong> Data passed via <code>postMessage()</code> is copied by default. For large amounts of data, consider using <code>Transferable</code> objects (like <code>ArrayBuffer</code>, <code>MessagePort</code>, <code>OffscreenCanvas</code>) to transfer ownership instead of copying, which is much more efficient.</li>
<li><strong>Error Handling:</strong> Implement error handling within your worker script using <code>try...catch</code> blocks and propagate errors back to the main thread via <code>postMessage()</code>. The main thread can also listen for <code>error</code> events on the worker.</li>
</ul>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// main.js</span>
myWorker.<span class="hljs-property">onerror</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Worker error:'</span>, e.<span class="hljs-property">message</span>);
};

<span class="hljs-comment">// worker.js</span>
self.<span class="hljs-property">onmessage</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// ... heavy calculation</span>
    } <span class="hljs-keyword">catch</span> (error) {
        self.<span class="hljs-title function_">postMessage</span>({ <span class="hljs-attr">type</span>: <span class="hljs-string">'error'</span>, <span class="hljs-attr">message</span>: error.<span class="hljs-property">message</span> });
    }
};
</code></pre>
<h2 id="impact-on-user-experience">Impact on User Experience</h2>
<p>Implementing Web Workers correctly dramatically enhances the user experience. Your applications will feel snappier and more professional. Users can continue interacting with the UI while complex processes run in the background. This responsiveness translates to higher user engagement, reduced bounce rates, and a more positive perception of your web application's performance. Consider using Web Workers for tasks like:</p>
<ul>
<li>Heavy array sorting or filtering</li>
<li>Image processing and manipulation</li>
<li>Data compression or decompression</li>
<li>Cryptographic calculations</li>
<li>Pre-fetching data in the background</li>
</ul>
<h2 id="key-takeaways">Key Takeaways</h2>
<p>Using Web Workers is a powerful technique for offloading intensive tasks and maintaining a responsive user interface. Remember these points:</p>
<ul>
<li>Keep the main thread free for UI updates.</li>
<li>Use <code>postMessage()</code> for asynchronous communication between main thread and worker.</li>
<li>Workers cannot directly access the DOM.</li>
<li>Consider <code>Transferable</code> objects for performance with large data.</li>
<li>Implement robust error handling.</li>
</ul>]]></content:encoded>
</item>
<item>
<title>Build Offline Web Apps with Service Workers</title>
<link>https://2haas.com/blog/build-offline-service-workers/</link>
<guid isPermaLink="true">https://2haas.com/blog/build-offline-service-workers/</guid>
<pubDate>Thu, 22 Oct 2026 17:35:15 GMT</pubDate>
<description>Discover how to use Service Workers to make your web applications work offline. Learn caching strategies and PWA principles.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>Modern web applications promise rich, dynamic experiences, but their reliability often crumbles with an unstable or absent internet connection. Users expect instant access and seamless functionality, regardless of network availability. Building an application that remains functional offline is a significant challenge for many developers, leading to frustrating user experiences and lost engagement when connectivity drops.</p>
<h2 id="understanding-service-workers">Understanding Service Workers</h2>
<p>Service Workers are specialized JavaScript files that act as a programmable proxy between your web application and the network. They run in the background, separate from the main browser thread, giving them powerful capabilities like intercepting network requests, caching assets, and delivering push notifications. Crucially, they enable "offline-first" architectures, allowing your application to function even when the user has no internet connection. They are a fundamental building block for Progressive Web Apps (PWAs).</p>
<p>Service Workers follow a lifecycle that includes registration, installation, and activation.
During the <strong>installation</strong> phase, the Service Worker can pre-cache essential resources.
In the <strong>activation</strong> phase, it takes control of the page and can clean up old caches.
After activation, it can intercept <strong>fetch</strong> requests, serving content from cache or the network as needed.</p>
<h2 id="registering-your-first-service-worker">Registering Your First Service Worker</h2>
<p>To get started, you first need to register your Service Worker file from your main application script. The Service Worker script itself is typically placed in the root directory of your project to maximize its scope.</p>
<p>Here is how you register a Service Worker:</p>
<ol>
<li>
<p><strong>Create your Service Worker file</strong>: Name it <code>sw.js</code> (or similar) and place it at the root of your project.</p>
</li>
<li>
<p><strong>Add registration code to your main JavaScript file</strong>:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">if</span> (<span class="hljs-string">'serviceWorker'</span> <span class="hljs-keyword">in</span> navigator) {
  <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'load'</span>, <span class="hljs-function">() =></span> {
    navigator.<span class="hljs-property">serviceWorker</span>.<span class="hljs-title function_">register</span>(<span class="hljs-string">'/sw.js'</span>)
      .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">registration</span> =></span> {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Service Worker registered with scope:'</span>, registration.<span class="hljs-property">scope</span>);
      })
      .<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">error</span> =></span> {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Service Worker registration failed:'</span>, error);
      });
  });
}
</code></pre>
<p>This code checks if Service Workers are supported by the browser. If so, it attempts to register <code>/sw.js</code> when the page finishes loading. The scope of <code>/sw.js</code> will be the directory it's in and all its subdirectories.</p>
</li>
</ol>
<h2 id="implementing-caching-strategies">Implementing Caching Strategies</h2>
<p>The core of an offline-capable application lies in its caching strategy. Service Workers use the Cache Storage API to store network responses.</p>
<p>Let's look at a common "Cache First, then Network" strategy for static assets and "Network First, then Cache" for dynamic content.</p>
<h3 id="cache-first-strategy-for-app-shellstatic-assets">Cache First Strategy (for App Shell/Static Assets)</h3>
<p>This strategy serves assets directly from the cache if available. If not, it falls back to the network and caches the response for future use. This is ideal for your application's shell (HTML, CSS, JavaScript, images) which rarely changes.</p>
<ol>
<li>
<p><strong>Define static assets to cache during installation</strong>: In <code>sw.js</code>:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">CACHE_NAME</span> = <span class="hljs-string">'my-app-cache-v1'</span>;
<span class="hljs-keyword">const</span> urlsToCache = [
  <span class="hljs-string">'/'</span>,
  <span class="hljs-string">'/index.html'</span>,
  <span class="hljs-string">'/styles/main.css'</span>,
  <span class="hljs-string">'/scripts/app.js'</span>,
  <span class="hljs-string">'/images/logo.png'</span>
];

self.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'install'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =></span> {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Service Worker: Install event'</span>);
  event.<span class="hljs-title function_">waitUntil</span>(
    caches.<span class="hljs-title function_">open</span>(<span class="hljs-variable constant_">CACHE_NAME</span>)
      .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">cache</span> =></span> {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Service Worker: Caching app shell'</span>);
        <span class="hljs-keyword">return</span> cache.<span class="hljs-title function_">addAll</span>(urlsToCache);
      })
  );
});
</code></pre>
</li>
<li>
<p><strong>Intercept fetch requests and serve from cache</strong>: In <code>sw.js</code>:</p>
<pre tabindex="0"><code class="hljs language-javascript">self.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =></span> {
  event.<span class="hljs-title function_">respondWith</span>(
    caches.<span class="hljs-title function_">match</span>(event.<span class="hljs-property">request</span>)
      .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">response</span> =></span> {
        <span class="hljs-comment">// Cache hit - return response</span>
        <span class="hljs-keyword">if</span> (response) {
          <span class="hljs-keyword">return</span> response;
        }
        <span class="hljs-comment">// No cache hit - fetch from network</span>
        <span class="hljs-keyword">return</span> <span class="hljs-title function_">fetch</span>(event.<span class="hljs-property">request</span>).<span class="hljs-title function_">then</span>(
          <span class="hljs-function"><span class="hljs-params">networkResponse</span> =></span> {
            <span class="hljs-comment">// Check if we received a valid response</span>
            <span class="hljs-keyword">if</span> (!networkResponse || networkResponse.<span class="hljs-property">status</span> !== <span class="hljs-number">200</span> || networkResponse.<span class="hljs-property">type</span> !== <span class="hljs-string">'basic'</span>) {
              <span class="hljs-keyword">return</span> networkResponse;
            }
            <span class="hljs-comment">// Clone the response because it's a stream and can only be consumed once</span>
            <span class="hljs-keyword">const</span> responseToCache = networkResponse.<span class="hljs-title function_">clone</span>();
            caches.<span class="hljs-title function_">open</span>(<span class="hljs-variable constant_">CACHE_NAME</span>)
              .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">cache</span> =></span> {
                cache.<span class="hljs-title function_">put</span>(event.<span class="hljs-property">request</span>, responseToCache);
              });
            <span class="hljs-keyword">return</span> networkResponse;
          }
        );
      })
  );
});
</code></pre>
</li>
</ol>
<h3 id="network-first-strategy-for-dynamic-content">Network First Strategy (for Dynamic Content)</h3>
<p>This strategy tries to fetch content from the network first. If the network fails, it falls back to the cache. This is suitable for dynamic data that needs to be as fresh as possible, but still needs offline availability.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// Inside your fetch event listener in sw.js, you might conditionally apply this:</span>
<span class="hljs-comment">// Example for an API call</span>
<span class="hljs-keyword">if</span> (event.<span class="hljs-property">request</span>.<span class="hljs-property">url</span>.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'https://api.example.com/'</span>)) {
  event.<span class="hljs-title function_">respondWith</span>(
    <span class="hljs-title function_">fetch</span>(event.<span class="hljs-property">request</span>)
      .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">networkResponse</span> =></span> {
        <span class="hljs-comment">// Cache the new network response</span>
        <span class="hljs-keyword">return</span> caches.<span class="hljs-title function_">open</span>(<span class="hljs-variable constant_">CACHE_NAME</span>).<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">cache</span> =></span> {
          cache.<span class="hljs-title function_">put</span>(event.<span class="hljs-property">request</span>, networkResponse.<span class="hljs-title function_">clone</span>());
          <span class="hljs-keyword">return</span> networkResponse;
        });
      })
      .<span class="hljs-title function_">catch</span>(<span class="hljs-function">() =></span> {
        <span class="hljs-comment">// Network failed, try to serve from cache</span>
        <span class="hljs-keyword">return</span> caches.<span class="hljs-title function_">match</span>(event.<span class="hljs-property">request</span>);
      })
  );
}
</code></pre>
<h2 id="handling-updates-and-versioning">Handling Updates and Versioning</h2>
<p>Cache management is crucial for keeping your application fresh. When you update your application, you typically want to update your cached assets.</p>
<ol>
<li>
<p><strong>Update <code>CACHE_NAME</code></strong>: Increment the version number (e.g., <code>my-app-cache-v2</code>).</p>
</li>
<li>
<p><strong>Cleanup old caches during activation</strong>: In <code>sw.js</code>:</p>
<pre tabindex="0"><code class="hljs language-javascript">self.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'activate'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =></span> {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Service Worker: Activate event'</span>);
  event.<span class="hljs-title function_">waitUntil</span>(
    caches.<span class="hljs-title function_">keys</span>().<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">cacheNames</span> =></span> {
      <span class="hljs-keyword">return</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>(
        cacheNames.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">cacheName</span> =></span> {
          <span class="hljs-keyword">if</span> (cacheName !== <span class="hljs-variable constant_">CACHE_NAME</span>) {
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Service Worker: Deleting old cache:'</span>, cacheName);
            <span class="hljs-keyword">return</span> caches.<span class="hljs-title function_">delete</span>(cacheName);
          }
        })
      );
    })
  );
});
</code></pre>
<p>This ensures that when a new Service Worker activates, it removes any old caches, preventing stale content from being served.</p>
</li>
</ol>
<h2 id="best-practices-for-offline-apps">Best Practices for Offline Apps</h2>
<ul>
<li><strong>HTTPS is mandatory</strong>: Service Workers only operate over HTTPS (except for <code>localhost</code> during development) for security reasons.</li>
<li><strong>Keep <code>sw.js</code> lean</strong>: Avoid heavy computations in the Service Worker file itself. Its primary job is request interception and caching.</li>
<li><strong>Test thoroughly</strong>: Use browser developer tools (Application tab) to inspect Service Worker status, caches, and network requests. Simulate offline mode.</li>
<li><strong>Provide user feedback</strong>: Inform users when they are offline or when new content is available and needs a refresh.</li>
<li><strong>Consider Workbox</strong>: For more complex PWA needs, libraries like Google's Workbox provide a higher-level abstraction for Service Worker development, simplifying caching strategies and build processes.</li>
</ul>
<h2 id="takeaway">Takeaway</h2>
<p>Embracing Service Workers transforms your web application into a robust, reliable experience that thrives even without a network connection. By strategically caching resources and managing updates, you can significantly improve user satisfaction and engagement. Start small, register your Service Worker, implement a basic cache, and then expand your offline capabilities.</p>]]></content:encoded>
</item>
<item>
<title>Seamless Cross-Language Modules with WebAssembly Components</title>
<link>https://2haas.com/blog/wasm-component-model-guide/</link>
<guid isPermaLink="true">https://2haas.com/blog/wasm-component-model-guide/</guid>
<pubDate>Wed, 21 Oct 2026 07:41:54 GMT</pubDate>
<description>Learn how the WebAssembly Component Model enables seamless, language-agnostic code integration. Discover how to build and leverage universal modules for modern applications.</description>
<category>Industry Updates</category>
<content:encoded><![CDATA[<p>The landscape of modern software development is increasingly polyglot. Teams often leverage multiple programming languages, each suited for specific tasks. However, integrating components written in different languages can introduce significant friction. Developers frequently encounter challenges with complex foreign function interfaces (FFI), bulky runtime dependencies, and the sheer effort required to share high-performance code across diverse ecosystems. This fragmentation hinders reusability and slows down innovation.</p>
<h2 id="what-is-the-webassembly-component-model">What is the WebAssembly Component Model?</h2>
<p>The WebAssembly Component Model is a groundbreaking specification that aims to revolutionize how software modules are built, composed, and executed across different programming languages and environments. At its core, it extends WebAssembly (WASM) beyond a low-level bytecode format, introducing a higher-level concept of 'components' with well-defined interfaces. This model allows a component written in one language (e.g., Rust, C++, Go) to be seamlessly called and composed with a component written in another (e.g., JavaScript, Python) without needing complex language-specific bindings or large runtime overhead.</p>
<p>Key aspects of the Component Model include:</p>
<ul>
<li><strong>Interface Definition Language (WIT):</strong> This declarative language is used to define the types and functions that components expose and require. WIT files act as contracts, ensuring interoperability.</li>
<li><strong>Canonical ABI:</strong> A standardized Application Binary Interface ensures that data types are passed consistently between components, regardless of their source language.</li>
<li><strong>Resource Management:</strong> The model includes mechanisms for managing resources (like file handles or network connections) safely across component boundaries.</li>
</ul>
<h2 id="how-it-works-defining-building-and-composing">How It Works: Defining, Building, and Composing</h2>
<p>Building and using WebAssembly components typically involves a few distinct steps:</p>
<ol>
<li>
<p><strong>Define Interfaces with WIT:</strong> Start by defining the public interface of your component using WIT. This specifies the functions, types, and resources it exports and imports.</p>
<pre tabindex="0"><code class="hljs language-wit">package example:greeter;

world greet-world {
  export greet: func(name: string) -> string;
}
</code></pre>
<p>This simple WIT defines a <code>greet</code> function that takes a string <code>name</code> and returns a string.</p>
</li>
<li>
<p><strong>Implement the Component:</strong> Write your component's logic in a language like Rust, C++, or TinyGo, compiling it to WebAssembly. Toolchains like <code>wasm-bindgen</code> for Rust or similar for other languages will generate the necessary boilerplate to adhere to the WIT definition and canonical ABI.</p>
<pre tabindex="0"><code class="hljs language-rust"><span class="hljs-comment">// src/lib.rs</span>
<span class="hljs-meta">#[export]</span>
<span class="hljs-keyword">fn</span> <span class="hljs-title function_">greet</span>(name: <span class="hljs-type">String</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">String</span> {
    <span class="hljs-built_in">format!</span>(<span class="hljs-string">"Hello, {}!"</span>, name)
}
</code></pre>
</li>
<li>
<p><strong>Package as a Component:</strong> Use tools like <code>wasm-tools</code> to 'componentize' your raw WebAssembly module. This process wraps your module with the necessary metadata from the WIT definition, making it a proper WebAssembly component.</p>
<pre tabindex="0"><code class="hljs language-bash">wasm-tools component new my_greeter.wasm -o my_greeter.component.wasm --wit greet-world.wit
</code></pre>
</li>
<li>
<p><strong>Host and Compose:</strong> Load the component in a host environment (e.g., a JavaScript application, a Python server, a WebAssembly System Interface (WASI) runtime). The host can then call the component's exported functions, and the component can call the host's imported functions, all type-safely and efficiently.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// conceptual JavaScript host code</span>
<span class="hljs-keyword">import</span> { greet } <span class="hljs-keyword">from</span> <span class="hljs-string">'my-greeter'</span>; <span class="hljs-comment">// imagine a JS binding</span>
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">greet</span>(<span class="hljs-string">'World'</span>)); <span class="hljs-comment">// Outputs: "Hello, World!"</span>
</code></pre>
</li>
</ol>
<h2 id="benefits-for-developers-and-organizations">Benefits for Developers and Organizations</h2>
<p>The WebAssembly Component Model offers a compelling array of advantages:</p>
<ul>
<li><strong>True Modularity:</strong> Create truly independent, reusable modules that are isolated and can be updated without affecting other parts of an application.</li>
<li><strong>Language Agnostic:</strong> Write components in the best language for the job, and use them universally across any host environment that supports WASM components.</li>
<li><strong>Enhanced Performance:</strong> WebAssembly's near-native execution speed translates directly to high-performance components, even when interacting across language boundaries.</li>
<li><strong>Improved Security:</strong> Components operate within a sandbox, providing a strong security boundary and allowing fine-grained permission control over their interactions with the host environment.</li>
<li><strong>Reduced Bundle Size:</strong> Components can be optimized for size, leading to faster load times and more efficient resource utilization.</li>
</ul>
<h2 id="practical-use-cases-and-future-outlook">Practical Use Cases and Future Outlook</h2>
<p>The Component Model opens up new possibilities across various domains:</p>
<ul>
<li><strong>Serverless Functions:</strong> Deploy lightweight, high-performance serverless functions written in any language.</li>
<li><strong>Plugin Architectures:</strong> Build robust and secure plugin systems where plugins are written in diverse languages.</li>
<li><strong>Edge Computing:</strong> Optimize execution at the edge by deploying small, efficient components.</li>
<li><strong>Desktop and Mobile Applications:</strong> Leverage WASM components for performance critical parts or cross-platform logic.</li>
<li><strong>Microservices:</strong> Create more granular and truly independent microservices.</li>
</ul>
<p>The WebAssembly ecosystem is rapidly evolving. The Component Model, coupled with WASI (WebAssembly System Interface) for standardized system access, promises to deliver a truly portable, secure, and performant computing paradigm that transcends traditional operating system and language boundaries. This will significantly impact how we think about software distribution, integration, and development.</p>
<h2 id="takeaway-checklist">Takeaway Checklist</h2>
<ul>
<li><strong>Understand WIT:</strong> Learn how to define interfaces using the WebAssembly Interface Type format.</li>
<li><strong>Explore Toolchains:</strong> Investigate language-specific toolchains for compiling to WASM components (e.g., <code>wasm-bindgen</code> for Rust).</li>
<li><strong>Experiment with <code>wasm-tools</code>:</strong> Familiarize yourself with the <code>wasm-tools</code> CLI for componentizing modules.</li>
<li><strong>Consider Use Cases:</strong> Identify areas in your projects where cross-language interoperability and modularity can be enhanced by adopting WASM components.</li>
</ul>]]></content:encoded>
</item>
<item>
<title>Playwright: Modern E2E Testing for Web Apps</title>
<link>https://2haas.com/blog/playwright-modern-e2e-testing/</link>
<guid isPermaLink="true">https://2haas.com/blog/playwright-modern-e2e-testing/</guid>
<pubDate>Tue, 20 Oct 2026 09:30:12 GMT</pubDate>
<description>Learn how Playwright provides fast, reliable, and cross browser end to end testing for your web applications, improving development quality.</description>
<category>Tool Reviews</category>
<content:encoded><![CDATA[<p>End-to-end (E2E) testing is crucial for ensuring the quality of web applications, simulating real user interactions to catch critical bugs before deployment. However, many developers struggle with existing E2E testing solutions. Common problems include flaky tests that fail intermittently, slow execution times, and complex setups for cross-browser compatibility, leading to unreliable test suites and hindering agile development.</p>
<h2 id="what-is-playwright">What is Playwright?</h2>
<p>Playwright is an open-source framework developed by Microsoft for reliable end-to-end testing of modern web apps. It automates Chromium, Firefox, and WebKit with a single API, allowing developers to write tests that run consistently across all major browsers. Unlike some older frameworks, Playwright operates directly with the browser's native capabilities, providing faster execution and a highly stable testing environment.</p>
<p>Key features of Playwright include its powerful auto-waiting mechanism, which automatically waits for elements to be actionable before performing operations, significantly reducing flakiness. It also supports parallel test execution out of the box, speeding up test suites, and offers a comprehensive set of tools like a code generator (Codegen) and a visual trace viewer for debugging tests.</p>
<p>Here is a quick comparison with other popular E2E testing tools:</p>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th align="left">Feature</th>
<th align="left">Playwright</th>
<th align="left">Cypress</th>
<th align="left">Selenium (WebDriver)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><strong>Architecture</strong></td>
<td align="left">Direct browser interaction</td>
<td align="left">Proxy-based, in-browser execution</td>
<td align="left">WebDriver protocol, external drivers</td>
</tr>
<tr>
<td align="left"><strong>Browser Support</strong></td>
<td align="left">Chromium, Firefox, WebKit, mobile emulation</td>
<td align="left">Chromium-based, Firefox (experimental), Edge</td>
<td align="left">All major browsers and operating systems</td>
</tr>
<tr>
<td align="left"><strong>Parallelization</strong></td>
<td align="left">Built-in (shard tests across workers)</td>
<td align="left">Limited, requires external tools/plugins</td>
<td align="left">Supports parallel execution via Grid</td>
</tr>
<tr>
<td align="left"><strong>Auto-waiting</strong></td>
<td align="left">Yes (robust and intelligent)</td>
<td align="left">Yes (automatically waits for elements)</td>
<td align="left">Requires explicit waits or custom implementations</td>
</tr>
<tr>
<td align="left"><strong>API</strong></td>
<td align="left">Modern, async/await focused, unified</td>
<td align="left">JavaScript-centric, chained commands</td>
<td align="left">Language bindings (Java, Python, JS, C# etc.)</td>
</tr>
<tr>
<td align="left"><strong>Network Control</strong></td>
<td align="left">Powerful interception and modification</td>
<td align="left">Robust network stubbing, route control</td>
<td align="left">Limited, relies on proxy setup or browser extensions</td>
</tr>
</tbody>
</table></div>
<h2 id="getting-started-with-playwright">Getting Started with Playwright</h2>
<p>Starting with Playwright is straightforward. You can initialize a new project and install the necessary browser binaries with a single command.</p>
<ol>
<li>
<p><strong>Initialize a new Playwright project:</strong></p>
<pre tabindex="0"><code class="hljs language-bash">npm init playwright@latest
<span class="hljs-comment"># or yarn create playwright</span>
<span class="hljs-comment"># or pnpm create playwright</span>
</code></pre>
<p>This command will guide you through setting up your first tests, choosing your language (TypeScript or JavaScript), and installing browser binaries.</p>
</li>
<li>
<p><strong>Run your first test (example created by <code>init</code>):</strong></p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// tests/example.spec.ts</span>
<span class="hljs-keyword">import</span> { test, expect } <span class="hljs-keyword">from</span> <span class="hljs-string">'@playwright/test'</span>;

<span class="hljs-title function_">test</span>(<span class="hljs-string">'has title'</span>, <span class="hljs-title function_">async</span> ({ page }) => {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">'https://playwright.dev/'</span>);
  <span class="hljs-comment">// Expect a title "to contain" a substring.</span>
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page).<span class="hljs-title function_">toHaveTitle</span>(<span class="hljs-regexp">/Playwright/</span>);
});

<span class="hljs-title function_">test</span>(<span class="hljs-string">'get started link'</span>, <span class="hljs-title function_">async</span> ({ page }) => {
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">'https://playwright.dev/'</span>);
  <span class="hljs-comment">// Click the get started link.</span>
  <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">'link'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Get started'</span> }).<span class="hljs-title function_">click</span>();
  <span class="hljs-comment">// Expects page to have a heading with the name of Installation.</span>
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">expect</span>(page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">'heading'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Installation'</span> })).<span class="hljs-title function_">toBeVisible</span>();
});
</code></pre>
</li>
<li>
<p><strong>Execute your tests:</strong></p>
<pre tabindex="0"><code class="hljs language-bash">npx playwright <span class="hljs-built_in">test</span>
</code></pre>
<p>Playwright will run the tests headlessly by default, meaning no browser window will open. You can add <code>--headed</code> to see the browser UI during execution.</p>
</li>
</ol>
<h2 id="advanced-features-for-robust-testing">Advanced Features for Robust Testing</h2>
<p>Playwright offers a rich API for complex testing scenarios:</p>
<ul>
<li><strong>Locators:</strong> Playwright encourages robust locators based on roles, text, and test IDs, making tests less brittle to UI changes. For instance, <code>page.getByRole('button', { name: 'Submit' })</code> is more reliable than a CSS selector.</li>
<li><strong>User Interactions:</strong> Simulate complex user flows including clicks, typing, hovering, dragging, and keyboard shortcuts.
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">'textbox'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Username'</span> }).<span class="hljs-title function_">fill</span>(<span class="hljs-string">'myuser'</span>);
<span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByLabel</span>(<span class="hljs-string">'Password'</span>).<span class="hljs-title function_">type</span>(<span class="hljs-string">'mypassword'</span>);
<span class="hljs-keyword">await</span> page.<span class="hljs-title function_">getByRole</span>(<span class="hljs-string">'button'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Log in'</span> }).<span class="hljs-title function_">click</span>();
</code></pre>
</li>
<li><strong>Network Interception:</strong> Control network requests, mock API responses, and test error conditions without modifying your backend.
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">await</span> page.<span class="hljs-title function_">route</span>(<span class="hljs-string">'**/api/users'</span>, <span class="hljs-keyword">async</span> route => {
  <span class="hljs-keyword">const</span> json = [{ <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'Alice'</span> }];
  <span class="hljs-keyword">await</span> route.<span class="hljs-title function_">fulfill</span>({ json });
});
<span class="hljs-keyword">await</span> page.<span class="hljs-title function_">goto</span>(<span class="hljs-string">'/users'</span>); <span class="hljs-comment">// Will load mocked user data</span>
</code></pre>
</li>
<li><strong>Authentication:</strong> Easily handle login flows and persist authentication states across tests to avoid repeated logins.</li>
</ul>
<h2 id="boosting-efficiency-with-playwright">Boosting Efficiency with Playwright</h2>
<p>Playwright provides tools to streamline your testing workflow:</p>
<ul>
<li><strong>Parallel Execution:</strong> By default, Playwright runs tests in parallel across worker processes, drastically reducing test suite execution time. You can configure the number of workers in <code>playwright.config.ts</code>.</li>
<li><strong>CI/CD Integration:</strong> Playwright integrates seamlessly with popular CI/CD pipelines like GitHub Actions, GitLab CI, and Jenkins, allowing you to run tests automatically on every code push.</li>
<li><strong>Trace Viewer:</strong> A powerful GUI tool that captures a full trace of your tests, including screenshots, DOM snapshots, and network logs, making debugging effortless. Run <code>npx playwright show-report</code> to view test reports and launch the trace viewer.</li>
</ul>
<h2 id="common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</h2>
<p>To maximize Playwright's benefits, consider these best practices:</p>
<ul>
<li><strong>Avoid <code>page.waitForTimeout()</code>:</strong> This hardcoded wait introduces flakiness and slows down tests. Rely on Playwright's auto-waiting or explicit <code>expect</code> assertions for conditions.</li>
<li><strong>Use Robust Locators:</strong> Prefer <code>getByRole</code>, <code>getByText</code>, <code>getByLabel</code>, or <code>getByTestId</code> over brittle CSS/XPath selectors that break with minor UI changes.</li>
<li><strong>Modularize Tests:</strong> Organize your test code using Page Object Model (POM) or a similar pattern to improve readability and maintainability, especially for large applications.</li>
<li><strong>Test Environment Configuration:</strong> Ensure your tests run in an isolated and consistent environment to prevent dependencies or state leaks between tests.</li>
</ul>
<p>Playwright offers a modern, reliable, and efficient solution for end-to-end testing, empowering development teams to deliver high-quality web applications with confidence. Its cross-browser support, powerful API, and developer-friendly tools make it an excellent choice for any serious web project.</p>]]></content:encoded>
</item>
<item>
<title>TypeScript 5.5 Beta Enhances Developer Experience</title>
<link>https://2haas.com/blog/typescript-5-5-beta-features/</link>
<guid isPermaLink="true">https://2haas.com/blog/typescript-5-5-beta-features/</guid>
<pubDate>Mon, 19 Oct 2026 10:19:20 GMT</pubDate>
<description>Learn about the new features in TypeScript 5.5 Beta. Discover how improved control flow analysis and type inference enhance your coding.</description>
<category>Tech News</category>
<content:encoded><![CDATA[<p>Developers often grapple with the intricacies of type safety, especially in large and complex JavaScript projects. While TypeScript significantly enhances this by providing static typing, there are still scenarios where the type system requires explicit declarations or struggles to infer types accurately within intricate code paths. This can lead to increased boilerplate, potential runtime errors, and a less fluid development experience. The need for a more intuitive and powerful type system is constant, pushing TypeScript towards greater precision and developer friendliness.</p>
<h2 id="typescript-55-beta-sharper-type-checking-for-developers">TypeScript 5.5 Beta: Sharper Type Checking for Developers</h2>
<p>The recent release of TypeScript 5.5 Beta promises to address many of these ongoing challenges. This update focuses on making the type checker even more intelligent and less intrusive, allowing developers to concentrate on logic rather than wrestling with type definitions. Key improvements in this version revolve around enhanced control flow analysis, smarter type predicate inference, and refinements to <code>import type</code> and <code>export type</code> syntax. These changes collectively aim to streamline development, reduce common type-related pitfalls, and ultimately lead to more robust applications. As a beta, it offers a preview of what's to come, inviting developers to test these features and provide feedback before the stable release.</p>
<h2 id="unlocking-precision-with-enhanced-control-flow-narrowing">Unlocking Precision with Enhanced Control Flow Narrowing</h2>
<p>Control flow narrowing is a cornerstone of TypeScript's type system, allowing the compiler to deduce types based on the path of execution. TypeScript 5.5 Beta significantly elevates this capability. Previously, developers might find the type checker conservative in complex <code>switch</code> statements or conditional expressions involving properties of union types. The 5.5 beta introduces more aggressive and accurate narrowing, especially when dealing with union types that have common properties or when destructuring objects. This means fewer explicit type assertions (<code>as Type</code>) are needed.</p>
<p>For instance, consider a scenario where you have a union of objects, each with a common <code>type</code> property. TypeScript 5.5 Beta improves how it narrows the type of the entire object based on checking that common property, even when destructuring:</p>
<pre tabindex="0"><code class="hljs language-typescript"><span class="hljs-keyword">type</span> <span class="hljs-title class_">ButtonState</span> = { <span class="hljs-attr">kind</span>: <span class="hljs-string">'loading'</span>; <span class="hljs-attr">progress</span>: <span class="hljs-built_in">number</span>; } | { <span class="hljs-attr">kind</span>: <span class="hljs-string">'error'</span>; <span class="hljs-attr">message</span>: <span class="hljs-built_in">string</span>; } | { <span class="hljs-attr">kind</span>: <span class="hljs-string">'success'</span>; };

<span class="hljs-keyword">function</span> <span class="hljs-title function_">renderButton</span>(<span class="hljs-params"><span class="hljs-attr">state</span>: <span class="hljs-title class_">ButtonState</span></span>) {
  <span class="hljs-keyword">switch</span> (state.<span class="hljs-property">kind</span>) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'loading'</span>:
      <span class="hljs-comment">// In 5.5 Beta, 'state' is correctly narrowed to { kind: 'loading'; progress: number; }</span>
      <span class="hljs-comment">// This allows direct access to 'state.progress' without needing further checks.</span>
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Loading... <span class="hljs-subst">${state.progress}</span>%`</span>);
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">'error'</span>:
      <span class="hljs-comment">// 'state' is { kind: 'error'; message: string; }</span>
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`Error: <span class="hljs-subst">${state.message}</span>`</span>);
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">'success'</span>:
      <span class="hljs-comment">// 'state' is { kind: 'success'; }</span>
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Success!'</span>);
      <span class="hljs-keyword">break</span>;
  }
}
</code></pre>
<p>This refined narrowing logic also extends to more advanced scenarios, such as when checking for <code>instanceof</code> within generic functions, making common patterns more intuitive and type-safe.</p>
<h2 id="smarter-type-predicate-inference-for-cleaner-code">Smarter Type Predicate Inference for Cleaner Code</h2>
<p>Type predicates, functions that inform the TypeScript compiler about the type of a variable, are incredibly powerful for creating custom type guards. In earlier versions, accurately defining and using these predicates could sometimes be a manual process, requiring careful annotation. TypeScript 5.5 Beta enhances the compiler's ability to <em>infer</em> type predicates, particularly in common patterns. This means you might write less boilerplate when creating functions that act as type guards.</p>
<p>Consider a simple <code>isNotNull</code> helper function:</p>
<pre tabindex="0"><code class="hljs language-typescript"><span class="hljs-keyword">function</span> isNotNull&#x3C;T>(<span class="hljs-attr">value</span>: T | <span class="hljs-literal">null</span>): value is T {
  <span class="hljs-keyword">return</span> value !== <span class="hljs-literal">null</span>;
}

<span class="hljs-keyword">const</span> <span class="hljs-attr">data</span>: (<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>)[] = [<span class="hljs-string">"hello"</span>, <span class="hljs-literal">null</span>, <span class="hljs-string">"world"</span>];
<span class="hljs-keyword">const</span> filteredData = data.<span class="hljs-title function_">filter</span>(isNotNull);

<span class="hljs-comment">// In previous versions, 'filteredData' might still be (string | null)[].</span>
<span class="hljs-comment">// With 5.5 Beta's improved inference, 'filteredData' is more reliably string[].</span>
<span class="hljs-comment">// This is especially true for more complex type guards.</span>
filteredData.<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">item</span> =></span> {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(item.<span class="hljs-title function_">toUpperCase</span>()); <span class="hljs-comment">// item is confidently 'string' here</span>
});
</code></pre>
<p>This improvement helps prevent common mistakes where developers forget to explicitly declare a type predicate, or where the compiler's understanding lagged behind the actual code logic. The result is more reliable type narrowing in array methods like <code>filter</code> and within conditional blocks.</p>
<h2 id="refined-import-type-and-export-type-directives">Refined <code>import type</code> and <code>export type</code> Directives</h2>
<p>TypeScript's <code>import type</code> and <code>export type</code> directives are crucial for ensuring that type-only imports and exports do not introduce runtime dependencies, which is vital for bundlers and tree-shaking optimizations. TypeScript 5.5 Beta introduces several refinements to how these directives are handled, particularly in conjunction with the <code>--verbatimModuleSyntax</code> compiler option.</p>
<p>The goal is to provide a more consistent and predictable experience:</p>
<ul>
<li><strong>Clearer intent:</strong> Explicitly marking type imports helps bundlers like Rollup or webpack confidently remove them during compilation.</li>
<li><strong>Reduced ambiguity:</strong> The compiler provides better feedback when an <code>import type</code> is actually importing a value or vice versa, preventing subtle errors.</li>
<li><strong>Improved compatibility:</strong> These refinements aim for better interoperability with future ECMAScript module features and tooling.</li>
</ul>
<p>While the syntax itself remains largely the same, the internal logic for identifying and handling type-only declarations has been strengthened. This leads to more accurate module graph analysis and better overall build performance when working with type-heavy libraries. Developers will find that the compiler is more intelligent about when it automatically converts an <code>import</code> to an <code>import type</code> under certain conditions, reducing the need for manual intervention.</p>
<h2 id="looking-ahead-the-impact-on-modern-web-development">Looking Ahead: The Impact on Modern Web Development</h2>
<p>The cumulative effect of these improvements in TypeScript 5.5 Beta is a significant leap forward in developer ergonomics and code reliability. By reducing the need for manual type assertions and making the type system smarter about control flow and predicate inference, developers can write cleaner, more concise code. This not only speeds up the development process but also lowers the cognitive load associated with managing complex type definitions. For large-scale applications and shared libraries, these enhancements translate directly into fewer bugs, easier maintenance, and a more robust foundation. Adopting TypeScript 5.5 Beta, even in its current preview state, allows teams to experience these benefits firsthand and prepare for a more streamlined future of type-safe JavaScript development.</p>
<p>TypeScript 5.5 Beta is a strong update focusing on developer experience and type system robustness. Key improvements in control flow narrowing, type predicate inference, and module syntax ensure cleaner and more reliable code.</p>
<p>Checklist for trying TypeScript 5.5 Beta:</p>
<ol>
<li>Install the beta: <code>npm install typescript@beta</code></li>
<li>Update your <code>tsconfig.json</code> to target a modern ECMAScript version if necessary.</li>
<li>Experiment with improved narrowing in complex <code>switch</code> statements or conditional logic.</li>
<li>Test type predicate inference with custom type guards.</li>
<li>Report any issues or provide feedback to the TypeScript team.</li>
</ol>]]></content:encoded>
</item>
<item>
<title>Guide to Implementing Web Push Notifications</title>
<link>https://2haas.com/blog/implement-web-push-notifications/</link>
<guid isPermaLink="true">https://2haas.com/blog/implement-web-push-notifications/</guid>
<pubDate>Sat, 17 Oct 2026 19:23:25 GMT</pubDate>
<description>Learn how to set up and send web push notifications to re-engage users. This guide covers client-side setup and server-side logic.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>Modern web applications constantly seek ways to keep users engaged and informed. A common challenge developers face is how to communicate with users effectively after they leave the website. Without a direct channel, users might forget to return, leading to decreased engagement and retention. This guide walks you through implementing web push notifications, a powerful tool for direct user communication.</p>
<h2 id="understanding-web-push-notifications">Understanding Web Push Notifications</h2>
<p>Web push notifications are messages that your website can send to users. These messages appear on their desktop or mobile device, similar to native application notifications. The unique aspect is that they function even when the user is not actively browsing your website. This capability relies on two key web technologies: Service Workers and the Push API.</p>
<p>Service Workers are JavaScript files that run in the background, separate from the main browser thread. They can intercept network requests, cache resources, and receive push messages from a server. The Push API, on the other hand, allows a web application to subscribe a user to push services and receive messages from a backend server.</p>
<h2 id="setting-up-the-client-side">Setting Up the Client-Side</h2>
<p>The client-side setup involves registering a service worker and requesting user permission.</p>
<ol>
<li>
<p><strong>Register Your Service Worker:</strong>
First, create a JavaScript file, for example, <code>service-worker.js</code>, in your project's root directory. Then, register it from your main application script.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// In your main application JavaScript file (e.g., app.js)</span>
<span class="hljs-keyword">if</span> (<span class="hljs-string">'serviceWorker'</span> <span class="hljs-keyword">in</span> navigator) {
  <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'load'</span>, <span class="hljs-function">() =></span> {
    navigator.<span class="hljs-property">serviceWorker</span>.<span class="hljs-title function_">register</span>(<span class="hljs-string">'/service-worker.js'</span>)
      .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">registration</span> =></span> {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Service Worker registered with scope:'</span>, registration.<span class="hljs-property">scope</span>);
      })
      .<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">error</span> =></span> {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Service Worker registration failed:'</span>, error);
      });
  });
}
</code></pre>
<p>Inside <code>service-worker.js</code>, you will handle push events:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// service-worker.js</span>
self.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'push'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =></span> {
  <span class="hljs-keyword">const</span> data = event.<span class="hljs-property">data</span>.<span class="hljs-title function_">json</span>();
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Push received:'</span>, data);

  <span class="hljs-keyword">const</span> title = data.<span class="hljs-property">title</span> || <span class="hljs-string">'2HAAS Notification'</span>;
  <span class="hljs-keyword">const</span> options = {
    <span class="hljs-attr">body</span>: data.<span class="hljs-property">body</span> || <span class="hljs-string">'You have a new message!'</span>,
    <span class="hljs-attr">icon</span>: <span class="hljs-string">'/images/icon-192x192.png'</span>, <span class="hljs-comment">// Ensure you have an icon</span>
    <span class="hljs-attr">badge</span>: <span class="hljs-string">'/images/badge-72x72.png'</span> <span class="hljs-comment">// For Android, optional</span>
  };

  event.<span class="hljs-title function_">waitUntil</span>(self.<span class="hljs-property">registration</span>.<span class="hljs-title function_">showNotification</span>(title, options));
});

self.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'notificationclick'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =></span> {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Notification clicked!'</span>);
  event.<span class="hljs-property">notification</span>.<span class="hljs-title function_">close</span>();
  <span class="hljs-comment">// You can open a new window or focus an existing one here</span>
  event.<span class="hljs-title function_">waitUntil</span>(
    clients.<span class="hljs-title function_">openWindow</span>(<span class="hljs-string">'https://2haas.com'</span>)
  );
});
</code></pre>
</li>
<li>
<p><strong>Request User Permission and Subscribe:</strong>
After the service worker is registered, you need to ask the user for permission to send notifications. If permission is granted, you can subscribe the user to a push service. This process generates a <code>PushSubscription</code> object.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// In your main application JavaScript file (e.g., app.js)</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">subscribeUser</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">if</span> (!(<span class="hljs-string">'PushManager'</span> <span class="hljs-keyword">in</span> <span class="hljs-variable language_">window</span>)) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">warn</span>(<span class="hljs-string">'Push notifications are not supported by this browser.'</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">const</span> permissionResult = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Notification</span>.<span class="hljs-title function_">requestPermission</span>();
  <span class="hljs-keyword">if</span> (permissionResult !== <span class="hljs-string">'granted'</span>) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">warn</span>(<span class="hljs-string">'Permission for notifications was not granted.'</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">const</span> serviceWorkerRegistration = <span class="hljs-keyword">await</span> navigator.<span class="hljs-property">serviceWorker</span>.<span class="hljs-property">ready</span>;
  <span class="hljs-keyword">const</span> applicationServerKey = <span class="hljs-title function_">urlBase64ToUint8Array</span>(<span class="hljs-string">'YOUR_VAPID_PUBLIC_KEY'</span>); <span class="hljs-comment">// Replace with your key</span>

  <span class="hljs-keyword">const</span> subscription = <span class="hljs-keyword">await</span> serviceWorkerRegistration.<span class="hljs-property">pushManager</span>.<span class="hljs-title function_">subscribe</span>({
    <span class="hljs-attr">userVisibleOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">applicationServerKey</span>: applicationServerKey
  });

  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'User is subscribed:'</span>, subscription);
  <span class="hljs-comment">// Send the subscription object to your backend server</span>
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">'/subscribe'</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> },
    <span class="hljs-attr">body</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(subscription)
  });
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Subscription sent to server.'</span>);
}

<span class="hljs-comment">// Utility function to convert VAPID key</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">urlBase64ToUint8Array</span>(<span class="hljs-params">base64String</span>) {
  <span class="hljs-keyword">const</span> padding = <span class="hljs-string">'='</span>.<span class="hljs-title function_">repeat</span>((<span class="hljs-number">4</span> - base64String.<span class="hljs-property">length</span> % <span class="hljs-number">4</span>) % <span class="hljs-number">4</span>);
  <span class="hljs-keyword">const</span> base64 = (base64String + padding)
    .<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/\-/g</span>, <span class="hljs-string">'+'</span>)
    .<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/_/g</span>, <span class="hljs-string">'/'</span>);

  <span class="hljs-keyword">const</span> rawData = <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">atob</span>(base64);
  <span class="hljs-keyword">const</span> outputArray = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Uint8Array</span>(rawData.<span class="hljs-property">length</span>);

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &#x3C; rawData.<span class="hljs-property">length</span>; ++i) {
    outputArray[i] = rawData.<span class="hljs-title function_">charCodeAt</span>(i);
  }
  <span class="hljs-keyword">return</span> outputArray;
}

<span class="hljs-comment">// Call subscribeUser() when appropriate, e.g., on a button click</span>
<span class="hljs-comment">// document.getElementById('subscribe-button').addEventListener('click', subscribeUser);</span>
</code></pre>
</li>
</ol>
<h2 id="server-side-implementation">Server-Side Implementation</h2>
<p>On the server, you need to store user subscriptions and send push notifications using a push library.</p>
<ol>
<li>
<p><strong>Generate VAPID Keys:</strong>
VAPID (Voluntary Application Server Identification) keys authenticate your server to the push service. You can generate a pair of public and private keys using a library or an online tool. Keep the private key secure and use the public key in your client-side code.</p>
<pre tabindex="0"><code class="hljs language-bash"><span class="hljs-comment"># Example using web-push-cli in Node.js environment</span>
npx web-push generate-vapid-keys
<span class="hljs-comment"># This will output:</span>
<span class="hljs-comment"># Public Key: B...</span>
<span class="hljs-comment"># Private Key: ...</span>
</code></pre>
</li>
<li>
<p><strong>Store Subscriptions:</strong>
When a user subscribes, your client-side code sends the <code>PushSubscription</code> object to your backend. Store this object, typically in a database, associated with the user. Each subscription contains an <code>endpoint</code> URL and <code>keys</code> (p256dh and auth).</p>
<pre tabindex="0"><code class="hljs language-json"><span class="hljs-comment">// Example PushSubscription object</span>
<span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"endpoint"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://fcm.googleapis.com/fcm/send/..."</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"expirationTime"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">null</span></span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"keys"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"p256dh"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"..."</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"auth"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"..."</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
</li>
<li>
<p><strong>Send Push Notifications:</strong>
To send a notification, your server uses a web push library (e.g., <code>web-push</code> for Node.js, <code>pywebpush</code> for Python). This library encrypts the payload and sends it to the push service endpoint specified in the user's subscription.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-comment">// Example Node.js server-side code using the 'web-push' library</span>
<span class="hljs-keyword">const</span> webpush = <span class="hljs-built_in">require</span>(<span class="hljs-string">'web-push'</span>);
<span class="hljs-comment">// Replace with your generated VAPID keys and contact email</span>
webpush.<span class="hljs-title function_">setVapidDetails</span>(
  <span class="hljs-string">'mailto:your_email@example.com'</span>,
  <span class="hljs-string">'YOUR_VAPID_PUBLIC_KEY'</span>,
  <span class="hljs-string">'YOUR_VAPID_PRIVATE_KEY'</span>
);

<span class="hljs-comment">// Assuming you have an array of stored subscriptions</span>
<span class="hljs-comment">// const subscriptions = getSubscriptionsFromDatabase();</span>

app.<span class="hljs-title function_">post</span>(<span class="hljs-string">'/send-notification'</span>, <span class="hljs-title function_">async</span> (req, res) => {
  <span class="hljs-keyword">const</span> payload = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>({
    <span class="hljs-attr">title</span>: <span class="hljs-string">'New Article from 2HAAS!'</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">'Check out our latest guide on Web Push Notifications.'</span>,
    <span class="hljs-attr">url</span>: <span class="hljs-string">'https://2haas.com/blog/implement-web-push-notifications'</span> <span class="hljs-comment">// Optional URL for notification click</span>
  });

  <span class="hljs-comment">// Iterate through stored subscriptions and send</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> subscription <span class="hljs-keyword">of</span> subscriptions) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> webpush.<span class="hljs-title function_">sendNotification</span>(subscription, payload);
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Notification sent to:'</span>, subscription.<span class="hljs-property">endpoint</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Failed to send notification:'</span>, error);
      <span class="hljs-comment">// Handle expired or invalid subscriptions (e.g., remove from database)</span>
    }
  }
  res.<span class="hljs-title function_">status</span>(<span class="hljs-number">200</span>).<span class="hljs-title function_">send</span>(<span class="hljs-string">'Notifications sent successfully.'</span>);
});
</code></pre>
</li>
</ol>
<h2 id="best-practices-and-considerations">Best Practices and Considerations</h2>
<ul>
<li><strong>User Experience:</strong> Always ask for permission at an opportune moment, not immediately on page load. Explain the value proposition.</li>
<li><strong>Payload Size:</strong> Keep notification payloads small. Many push services have size limits (e.g., 4KB).</li>
<li><strong>Expiration:</strong> Push services might hold notifications for a limited time if the device is offline. You can specify a <code>ttl</code> (Time To Live) in your push request.</li>
<li><strong>Unsubscribing:</strong> Provide a clear way for users to unsubscribe. Remove their subscription from your database when they do.</li>
<li><strong>Error Handling:</strong> Implement robust error handling on the server, especially for <code>410 Gone</code> status codes, which indicate an expired or invalid subscription that should be removed.</li>
</ul>
<h2 id="your-notification-checklist">Your Notification Checklist</h2>
<p>Implementing web push notifications can significantly boost user engagement. Follow these steps:</p>
<ol>
<li>Ensure your application is served over HTTPS.</li>
<li>Create and register your <code>service-worker.js</code>.</li>
<li>Generate VAPID keys for server authentication.</li>
<li>Implement client-side permission requests and subscription logic.</li>
<li>Develop server-side code to store subscriptions and send notifications.</li>
<li>Test thoroughly across different browsers and devices.</li>
<li>Provide a clear unsubscribe mechanism for users.</li>
</ol>]]></content:encoded>
</item>
<item>
<title>WebGPU: Next-Gen Web Graphics Performance</title>
<link>https://2haas.com/blog/webgpu-next-gen-graphics/</link>
<guid isPermaLink="true">https://2haas.com/blog/webgpu-next-gen-graphics/</guid>
<pubDate>Fri, 16 Oct 2026 21:53:55 GMT</pubDate>
<description>Discover how WebGPU revolutionizes web graphics, offering significantly enhanced performance and advanced GPU capabilities directly in the browser for richer applications.</description>
<category>Tech News</category>
<content:encoded><![CDATA[<p>Developing high-performance graphics and complex computations directly within web browsers has long presented a significant challenge. Traditional web graphics APIs often struggled with performance bottlenecks, intricate setup, and a lack of direct access to modern GPU features, limiting the ambition of web applications.</p>
<h2 id="what-is-webgpu">What is WebGPU?</h2>
<p>WebGPU is a new web graphics API that provides a modern, low-level interface for performing GPU computations and rendering high-performance 3D graphics in web browsers. It is designed as the successor to WebGL, offering a more direct and efficient pathway to the underlying hardware. Inspired by native APIs like Vulkan, Metal, and DirectX 12, WebGPU brings a new level of power and control to web developers, enabling highly complex and performant applications that were previously confined to native platforms.</p>
<p>This API allows developers to write code that interacts with the GPU at a more granular level, optimizing resource management, command submission, and shader execution. It is built for the modern era of parallel processing and heterogeneous computing, making it ideal for tasks ranging from advanced 3D rendering to machine learning inference.</p>
<h2 id="why-webgpu-matters-limitations-of-webgl">Why WebGPU Matters: Limitations of WebGL</h2>
<p>For many years, WebGL was the standard for 3D graphics on the web. While revolutionary in its time, WebGL, based on OpenGL ES 2.0/3.0, faced several limitations:</p>
<ul>
<li><strong>Complexity:</strong> WebGL's stateful API could be cumbersome and error-prone for complex rendering pipelines.</li>
<li><strong>Performance Bottlenecks:</strong> It often introduced overhead due to its older design and impedance mismatch with modern GPU architectures.</li>
<li><strong>Limited Features:</strong> WebGL lacked direct support for many advanced GPU features available in contemporary native graphics APIs.</li>
<li><strong>Driver Issues:</strong> Performance and compatibility could vary significantly across different devices and drivers, leading to inconsistent experiences.</li>
</ul>
<p>These issues meant that pushing the boundaries of web graphics often involved significant workarounds, sacrificing performance, or simply settling for less ambitious visual experiences. WebGPU directly addresses these core problems with a more explicit, modern, and performant design.</p>
<h2 id="how-webgpu-delivers-performance">How WebGPU Delivers Performance</h2>
<p>WebGPU achieves its superior performance and flexibility through several key architectural improvements:</p>
<ul>
<li><strong>Stateless API Design:</strong> Unlike WebGL, WebGPU uses a stateless, object-oriented approach. Developers explicitly manage resources and command submissions, leading to more predictable performance and easier debugging.</li>
<li><strong>Asynchronous Operations:</strong> Most operations in WebGPU are asynchronous, allowing the main thread to remain responsive while GPU computations run in the background. This is crucial for smooth user interfaces.</li>
<li><strong>Compute Shaders:</strong> A significant addition is support for compute shaders, which enable general-purpose GPU computing. This means developers can offload complex parallel tasks, such as physics simulations, data processing, or AI model inference, directly to the GPU.</li>
<li><strong>Modern Pipeline Model:</strong> WebGPU's pipeline creation is explicit and immutable, allowing the browser and GPU driver to pre-optimize the pipeline state, reducing runtime overhead.</li>
</ul>
<h2 id="practical-applications-and-use-cases">Practical Applications and Use Cases</h2>
<p>The capabilities unlocked by WebGPU open up a vast array of possibilities for web applications:</p>
<ul>
<li><strong>Advanced 3D Modeling and Design Tools:</strong> Run professional-grade CAD/CAM software or 3D content creation tools directly in the browser with desktop-like performance.</li>
<li><strong>High-Fidelity Games:</strong> Develop visually stunning and performance-intensive games that rival native applications, complete with realistic lighting, complex particle systems, and large worlds.</li>
<li><strong>Data Visualization and Scientific Computing:</strong> Render massive datasets, complex simulations, and scientific visualizations with unprecedented speed and detail.</li>
<li><strong>Machine Learning in the Browser:</strong> Perform local inference for AI models more efficiently, powering features like real-time image processing, natural language understanding, or advanced AR/VR experiences without relying on cloud resources.</li>
<li><strong>Video Editing and Image Processing:</strong> Implement powerful, real-time filters and effects for video and image manipulation directly on the client side.</li>
</ul>
<h2 id="getting-started-with-webgpu">Getting Started with WebGPU</h2>
<p>To begin using WebGPU, ensure you are running a compatible browser (e.g., Chrome 113+, Firefox Nightly, Edge). The API is exposed through the <code>navigator.gpu</code> object. Here's a basic setup pattern:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">initializeWebGPU</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">if</span> (!navigator.<span class="hljs-property">gpu</span>) {
    <span class="hljs-title function_">alert</span>(<span class="hljs-string">"WebGPU is not supported in this browser."</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// Request a GPU adapter</span>
  <span class="hljs-keyword">const</span> adapter = <span class="hljs-keyword">await</span> navigator.<span class="hljs-property">gpu</span>.<span class="hljs-title function_">requestAdapter</span>();
  <span class="hljs-keyword">if</span> (!adapter) {
    <span class="hljs-title function_">alert</span>(<span class="hljs-string">"No GPU adapter found."</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// Request a GPU device from the adapter</span>
  <span class="hljs-keyword">const</span> device = <span class="hljs-keyword">await</span> adapter.<span class="hljs-title function_">requestDevice</span>();
  device.<span class="hljs-property">lost</span>.<span class="hljs-title function_">then</span>(<span class="hljs-function">() =></span> {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'WebGPU device lost'</span>);
  });

  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'WebGPU device obtained:'</span>, device);

  <span class="hljs-comment">// Get a reference to the canvas context for rendering</span>
  <span class="hljs-keyword">const</span> canvas = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'gpu-canvas'</span>);
  <span class="hljs-keyword">const</span> context = canvas.<span class="hljs-title function_">getContext</span>(<span class="hljs-string">'webgpu'</span>);

  <span class="hljs-keyword">if</span> (!context) {
    <span class="hljs-title function_">alert</span>(<span class="hljs-string">"Could not get WebGPU canvas context."</span>);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// Configure the canvas context</span>
  <span class="hljs-keyword">const</span> presentationFormat = navigator.<span class="hljs-property">gpu</span>.<span class="hljs-title function_">getPreferredCanvasFormat</span>();
  context.<span class="hljs-title function_">configure</span>({
    <span class="hljs-attr">device</span>: device,
    <span class="hljs-attr">format</span>: presentationFormat,
    <span class="hljs-attr">alphaMode</span>: <span class="hljs-string">'opaque'</span>,
  });

  <span class="hljs-comment">// Now you have a device and context to start rendering or computing.</span>
  <span class="hljs-comment">// e.g., create buffers, textures, render pipelines, command encoders.</span>

  <span class="hljs-keyword">return</span> { device, context, presentationFormat };
}

<span class="hljs-title function_">initializeWebGPU</span>().<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">{ device, context, presentationFormat }</span>) =></span> {
  <span class="hljs-keyword">if</span> (device) {
    <span class="hljs-comment">// Example: Clear the canvas to a specific color</span>
    <span class="hljs-keyword">const</span> commandEncoder = device.<span class="hljs-title function_">createCommandEncoder</span>();
    <span class="hljs-keyword">const</span> renderPassDescriptor = {
      <span class="hljs-attr">colorAttachments</span>: [
        {
          <span class="hljs-attr">view</span>: context.<span class="hljs-title function_">getCurrentTexture</span>().<span class="hljs-title function_">createView</span>(),
          <span class="hljs-attr">clearValue</span>: { <span class="hljs-attr">r</span>: <span class="hljs-number">0.1</span>, <span class="hljs-attr">g</span>: <span class="hljs-number">0.2</span>, <span class="hljs-attr">b</span>: <span class="hljs-number">0.3</span>, <span class="hljs-attr">a</span>: <span class="hljs-number">1.0</span> }, <span class="hljs-comment">// Dark blue color</span>
          <span class="hljs-attr">loadOp</span>: <span class="hljs-string">'clear'</span>,
          <span class="hljs-attr">storeOp</span>: <span class="hljs-string">'store'</span>,
        },
      ],
    };
    <span class="hljs-keyword">const</span> passEncoder = commandEncoder.<span class="hljs-title function_">beginRenderPass</span>(renderPassDescriptor);
    passEncoder.<span class="hljs-title function_">end</span>();
    device.<span class="hljs-property">queue</span>.<span class="hljs-title function_">submit</span>([commandEncoder.<span class="hljs-title function_">finish</span>()]);
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Canvas cleared to dark blue.'</span>);
  }
}).<span class="hljs-title function_">catch</span>(<span class="hljs-function"><span class="hljs-params">error</span> =></span> {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Failed to initialize WebGPU:'</span>, error);
});
</code></pre>
<p>This snippet provides the fundamental steps: checking for <code>navigator.gpu</code>, requesting an adapter and a device, and configuring a canvas context. From here, developers define render pipelines, create buffers for vertex data, and write shaders in WGSL (WebGPU Shading Language) to dictate how graphics are processed.</p>
<h2 id="the-future-of-web-graphics">The Future of Web Graphics</h2>
<p>WebGPU marks a significant turning point for web development. It empowers developers to create richer, more interactive, and performant web experiences, effectively narrowing the gap between web and native applications. As browser support matures and more frameworks and libraries integrate WebGPU, we can expect a new wave of innovative web applications that leverage the full power of modern GPUs.</p>
<p><strong>Key Takeaways for Developers:</strong></p>
<ul>
<li><strong>Embrace WebGPU:</strong> Start learning the API to prepare for the future of web graphics.</li>
<li><strong>Consider Performance:</strong> Use WebGPU for tasks requiring high graphical fidelity or extensive parallel computation.</li>
<li><strong>Explore WGSL:</strong> Understand the WebGPU Shading Language for writing efficient GPU code.</li>
<li><strong>Stay Updated:</strong> Follow browser releases and W3C specifications for the latest advancements and best practices.</li>
</ul>]]></content:encoded>
</item>
<item>
<title>Master CSS Custom Properties for Dynamic Styles</title>
<link>https://2haas.com/blog/master-css-custom-properties/</link>
<guid isPermaLink="true">https://2haas.com/blog/master-css-custom-properties/</guid>
<pubDate>Thu, 15 Oct 2026 19:32:23 GMT</pubDate>
<description>Learn how CSS custom properties (variables) simplify your stylesheet management, enable dynamic theming, and improve maintainability for any web project.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>Are you tired of endlessly repeating color codes or font sizes throughout your CSS files? Does updating a single design element feel like a daunting task that involves searching and replacing across dozens of lines? CSS custom properties, often called CSS variables, offer a powerful native solution to these common front-end development headaches, making your stylesheets more dynamic, maintainable, and organized.</p>
<h2 id="what-are-css-custom-properties">What Are CSS Custom Properties?</h2>
<p>CSS custom properties are entities defined by CSS authors that contain specific values to be reused throughout a document. They act like variables in programming languages. You define them once and use them many times. They are particularly powerful because they respect the cascade, meaning their values can inherit and be overridden at different levels of your stylesheet. This allows for incredibly flexible and dynamic styling solutions.</p>
<h2 id="how-to-declare-and-use-custom-properties">How to Declare and Use Custom Properties</h2>
<p>Declaring a custom property is straightforward. You prefix the property name with two hyphens (<code>--</code>). You can declare them globally at the <code>:root</code> pseudo-class for site-wide availability or within specific selectors for localized scope.</p>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-comment">/* Global declaration */</span>
<span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attr">--primary-color</span>: <span class="hljs-number">#cc3f0c</span>;
  <span class="hljs-attr">--secondary-color</span>: <span class="hljs-number">#1a1a1a</span>;
  <span class="hljs-attr">--spacing-unit</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-comment">/* Local declaration */</span>
<span class="hljs-selector-class">.card</span> {
  <span class="hljs-attr">--card-bg</span>: <span class="hljs-number">#fffafa</span>;
  <span class="hljs-attr">--card-border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-built_in">var</span>(--secondary-color);
}
</code></pre>
<p>To use a custom property, you employ the <code>var()</code> function, passing the property name as an argument. You can also provide a fallback value if the custom property is not defined or is invalid.</p>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--primary-color);
  <span class="hljs-attribute">margin</span>: <span class="hljs-built_in">var</span>(--spacing-unit);
}

<span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--secondary-color, black); <span class="hljs-comment">/* Fallback to black if --secondary-color is not defined */</span>
}

<span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--card-bg);
  <span class="hljs-attribute">border</span>: <span class="hljs-built_in">var</span>(--card-border);
  <span class="hljs-attribute">padding</span>: <span class="hljs-built_in">var</span>(--spacing-unit);
}
</code></pre>
<h2 id="leveraging-custom-properties-for-theming">Leveraging Custom Properties for Theming</h2>
<p>One of the most compelling use cases for CSS custom properties is dynamic theming. You can define a set of theme-dependent variables and then change their values based on a parent selector or even a media query for user preferences like <code>prefers-color-scheme</code>.</p>
<p>Let's illustrate with a simple dark and light theme example:</p>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-selector-pseudo">:root</span> { <span class="hljs-comment">/* Light theme defaults */</span>
  <span class="hljs-attr">--text-color</span>: <span class="hljs-number">#1a1a1a</span>;
  <span class="hljs-attr">--background-color</span>: <span class="hljs-number">#fffafa</span>;
  <span class="hljs-attr">--border-color</span>: <span class="hljs-number">#cc3f0c</span>;
}

<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--text-color);
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--background-color);
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-built_in">var</span>(--border-color);
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">transition</span>: background-color <span class="hljs-number">0.3s</span>, color <span class="hljs-number">0.3s</span>;
}

<span class="hljs-comment">/* Dark theme toggle */</span>
<span class="hljs-selector-tag">body</span><span class="hljs-selector-class">.dark-theme</span> {
  <span class="hljs-attr">--text-color</span>: <span class="hljs-number">#fffafa</span>;
  <span class="hljs-attr">--background-color</span>: <span class="hljs-number">#1a1a1a</span>;
  <span class="hljs-attr">--border-color</span>: <span class="hljs-number">#cc3f0c</span>;
}
</code></pre>
<p>You can then toggle the <code>dark-theme</code> class on the <code>body</code> element using JavaScript to switch themes instantly.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'theme-toggle'</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {
  <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>.<span class="hljs-property">classList</span>.<span class="hljs-title function_">toggle</span>(<span class="hljs-string">'dark-theme'</span>);
});
</code></pre>
<p>This approach greatly simplifies theme management compared to maintaining separate stylesheets or complex preprocessor logic.</p>
<h2 id="advantages-of-using-custom-properties">Advantages of Using Custom Properties</h2>
<ul>
<li><strong>Maintainability</strong>: Update a single variable definition, and the change propagates everywhere it's used. This dramatically reduces maintenance time and error potential.</li>
<li><strong>Readability</strong>: Descriptive variable names (<code>--primary-color</code> instead of <code>#cc3f0c</code>) make your CSS easier to understand for anyone reading the code.</li>
<li><strong>Flexibility</strong>: Easily change values at runtime using JavaScript or based on media queries for responsive or dynamic designs.</li>
<li><strong>Performance</strong>: Reduces file size by eliminating repetitive values and avoids the need for CSS preprocessors just for variables, leading to faster loading times.</li>
<li><strong>Debugging</strong>: Modern browser developer tools allow you to inspect and modify custom property values directly, making debugging styles much easier.</li>
</ul>
<h2 id="common-mistakes-and-best-practices">Common Mistakes and Best Practices</h2>
<ul>
<li><strong>Avoid over-scoping</strong>: While local variables are useful, don't define a variable within every single selector. Use <code>:root</code> for global values and scope only when necessary.</li>
<li><strong>Meaningful names</strong>: Choose descriptive names that reflect the variable's purpose, not just its value (e.g., <code>--heading-font</code> instead of <code>--font-24px</code>).</li>
<li><strong>Fallback values</strong>: Always consider providing a fallback value in <code>var()</code> for robustness, especially when dealing with potentially undefined variables or browser support.</li>
<li><strong>Units</strong>: Custom properties store raw values. If you're defining a length or size, include the unit (e.g., <code>--spacing-unit: 1rem;</code> not <code>--spacing-unit: 1;</code>).</li>
<li><strong>Browser Support</strong>: CSS custom properties are widely supported in modern browsers. caniuse.com reports over 97% global support. However, for extremely old browsers, a fallback mechanism (e.g., using a preprocessor to compile static values) might be necessary.</li>
</ul>
<h2 id="enhance-your-stylesheets-today">Enhance Your Stylesheets Today</h2>
<p>CSS custom properties are a powerful, native CSS feature that brings the benefits of variables to your stylesheets without relying on preprocessors. They empower you to create more maintainable, flexible, and dynamic designs with less effort. Start incorporating them into your projects to streamline your workflow, improve code clarity, and unlock new possibilities for responsive and themed web experiences. Embrace this feature for cleaner, smarter, and more efficient CSS development.</p>]]></content:encoded>
</item>
<item>
<title>Simplify Popups with HTML Popover API</title>
<link>https://2haas.com/blog/simplify-popups-html-popover-api/</link>
<guid isPermaLink="true">https://2haas.com/blog/simplify-popups-html-popover-api/</guid>
<pubDate>Thu, 15 Oct 2026 01:40:57 GMT</pubDate>
<description>Learn how to effortlessly create accessible popups, tooltips, and modals using the native HTML Popover API, reducing custom JavaScript and improving user experience.</description>
<category>Code Snippets</category>
<content:encoded><![CDATA[<p>Building robust and accessible ephemeral UI elements like popups, tooltips, and modals has long been a complex task for web developers. These components often require intricate JavaScript to handle focus management, keyboard navigation, light-dismiss behavior, and correct ARIA attributes, leading to fragile and high-maintenance codebases. The new HTML Popover API offers a native, declarative solution to these challenges, significantly simplifying development.</p>
<h2 id="what-is-the-popover-api">What is the Popover API?</h2>
<p>The Popover API introduces a standard HTML attribute, <code>popover</code>, that allows any element to be promoted to the browser's "top layer." The top layer is a new rendering layer where elements like full-screen video, <code>&#x3C;dialog></code> elements, and now popovers are displayed, ensuring they always appear above all other document content without worrying about <code>z-index</code> conflicts. The browser handles essential behaviors such as light-dismissal (clicking outside or pressing Esc to close), focus management, and accessibility, reducing the need for custom JavaScript.</p>
<p>Unlike the <code>&#x3C;dialog></code> element, which is typically used for modal, interruptive experiences, popovers are designed for non-modal, transient UI elements. Multiple popovers can be open simultaneously, making them ideal for context menus, notifications, or subtle hints.</p>
<h2 id="implementing-a-basic-popover">Implementing a Basic Popover</h2>
<p>Creating a basic popover is incredibly straightforward, often requiring just HTML. You apply the <code>popover</code> attribute to the element you want to appear as a popover and then link it to a control element (like a button) using the <code>popovertarget</code> attribute.</p>
<p>Let's consider a simple example where a button opens an information box:</p>
<pre tabindex="0"><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">popovertarget</span>=<span class="hljs-string">"info-popup"</span>></span>Show Info<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>

<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info-popup"</span> <span class="hljs-attr">popover</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">h3</span>></span>Important Information<span class="hljs-tag">&#x3C;/<span class="hljs-name">h3</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>This is a popover containing essential details about the product. It disappears when you click outside or press Esc.<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">popovertargetaction</span>=<span class="hljs-string">"hide"</span> <span class="hljs-attr">popovertarget</span>=<span class="hljs-string">"info-popup"</span>></span>Close<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
</code></pre>
<p>In this snippet:</p>
<ul>
<li>The <code>&#x3C;div></code> with <code>id="info-popup"</code> has the <code>popover</code> attribute, declaring it as a popover element. By default, this is an "auto" popover, meaning it will automatically close when the user interacts outside of it or presses the Esc key.</li>
<li>The <code>&#x3C;button></code> has <code>popovertarget="info-popup"</code>, which tells the browser that clicking this button should open the element with <code>id="info-popup"</code>.</li>
<li>The inner "Close" button uses <code>popovertargetaction="hide"</code> and <code>popovertarget="info-popup"</code> to explicitly close the popover. Other possible actions are <code>"show"</code> and <code>"toggle"</code>.</li>
</ul>
<h2 id="controlling-popovers-with-javascript">Controlling Popovers with JavaScript</h2>
<p>While declarative HTML is powerful for simple cases, JavaScript provides finer control over popovers, especially for more dynamic interactions. Popover elements expose <code>showPopover()</code>, <code>hidePopover()</code>, and <code>togglePopover()</code> methods.</p>
<p>To use JavaScript for control, you typically mark the popover with <code>popover="manual"</code>. This prevents the <code>popovertarget</code> attribute from automatically toggling it, allowing you to manage its state programmatically.</p>
<pre tabindex="0"><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"js-trigger"</span>></span>Open JS Popover<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>

<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"js-controlled-popover"</span> <span class="hljs-attr">popover</span>=<span class="hljs-string">"manual"</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>This popover is manually controlled by JavaScript.<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"js-close-button"</span>></span>Close<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>

<span class="hljs-tag">&#x3C;<span class="hljs-name">script</span>></span><span class="javascript">
  <span class="hljs-keyword">const</span> triggerBtn = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'js-trigger'</span>);
  <span class="hljs-keyword">const</span> closeBtn = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'js-close-button'</span>);
  <span class="hljs-keyword">const</span> jsPopover = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'js-controlled-popover'</span>);

  triggerBtn.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {
    jsPopover.<span class="hljs-title function_">showPopover</span>(); <span class="hljs-comment">// Show the popover</span>
  });

  closeBtn.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =></span> {
    jsPopover.<span class="hljs-title function_">hidePopover</span>(); <span class="hljs-comment">// Hide the popover</span>
  });

  <span class="hljs-comment">// You can also use togglePopover() for a single button that opens and closes</span>
  <span class="hljs-comment">// triggerBtn.addEventListener('click', () => {</span>
  <span class="hljs-comment">//   jsPopover.togglePopover();</span>
  <span class="hljs-comment">// });</span>
</span><span class="hljs-tag">&#x3C;/<span class="hljs-name">script</span>></span>
</code></pre>
<p>Using <code>popover="manual"</code> gives you full control, while <code>popover</code> (or <code>popover="auto"</code>) handles automatic showing/hiding via <code>popovertarget</code>.</p>
<h2 id="styling-and-accessibility-benefits">Styling and Accessibility Benefits</h2>
<p>Popovers, being in the top layer, inherit styles from the document but have a default minimal appearance. You can style them using standard CSS properties. A particularly useful feature for styling "auto" popovers is the <code>::backdrop</code> pseudo-element.</p>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-comment">/* Style the popover content */</span>
<span class="hljs-selector-id">#info-popup</span> {
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#cc3f0c</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fffafa</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">12px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>);
}

<span class="hljs-comment">/* Style the backdrop for auto popovers */</span>
<span class="hljs-selector-id">#info-popup</span><span class="hljs-selector-pseudo">::backdrop</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">26</span>, <span class="hljs-number">26</span>, <span class="hljs-number">26</span>, <span class="hljs-number">0.4</span>);
  <span class="hljs-attribute">backdrop-filter</span>: <span class="hljs-built_in">blur</span>(<span class="hljs-number">3px</span>);
}
</code></pre>
<p>From an accessibility standpoint, the Popover API is a game-changer. Browsers automatically handle several crucial aspects:</p>
<ul>
<li><strong>Focus Management:</strong> When a popover opens, focus moves into the popover, allowing users to interact with its content immediately. When it closes (via light-dismiss or explicit action), focus returns to the element that triggered it.</li>
<li><strong>ARIA Attributes:</strong> The browser implicitly handles relevant ARIA attributes and roles, ensuring screen readers announce the popover correctly.</li>
<li><strong>Keyboard Interaction:</strong> The Esc key closes popovers by default, a standard and expected behavior for temporary UI.</li>
</ul>
<p>These built-in features drastically reduce the complexity and potential for errors when making accessible components.</p>
<h2 id="browser-support-and-considerations">Browser Support and Considerations</h2>
<p>The Popover API enjoys excellent modern browser support. It is available in Chrome 114+, Edge 114+, Firefox 125+, and Safari 17.0+. This widespread adoption means you can confidently start integrating it into your projects today.</p>
<p>For environments requiring support for older browsers, a polyfill might be necessary. While the <code>&#x3C;dialog></code> element remains a good choice for truly modal experiences that block interaction with the rest of the page, the Popover API excels for non-modal, transient content. Consider which mental model best fits your UI requirements.</p>
<h2 id="conclusion-embrace-native-popovers">Conclusion: Embrace Native Popovers</h2>
<p>The HTML Popover API represents a significant step forward in web development, offering a standardized, accessible, and less JavaScript-heavy approach to creating ephemeral UI elements. By leveraging this native functionality, developers can build more robust, performant, and user-friendly web applications with less effort. It's time to move beyond complex custom solutions and embrace the power of the platform.</p>
<h3 id="your-popover-api-checklist">Your Popover API Checklist:</h3>
<ul>
<li>Apply the <code>popover</code> attribute to your popup content.</li>
<li>Use <code>popovertarget</code> on your trigger element to link it.</li>
<li>For explicit closing, use <code>popovertargetaction="hide"</code>.</li>
<li>Control more complex scenarios with <code>popover="manual"</code> and JavaScript methods (<code>showPopover()</code>, <code>hidePopover()</code>, <code>togglePopover()</code>).</li>
<li>Style your popover and its <code>::backdrop</code> with standard CSS.</li>
<li>Enjoy improved accessibility and reduced JavaScript boilerplate.</li>
</ul>]]></content:encoded>
</item>
<item>
<title>Bun 1.1 Release: Faster JavaScript Development</title>
<link>https://2haas.com/blog/bun-1-1-faster-javascript/</link>
<guid isPermaLink="true">https://2haas.com/blog/bun-1-1-faster-javascript/</guid>
<pubDate>Tue, 13 Oct 2026 22:22:51 GMT</pubDate>
<description>Learn how Bun 1.1 enhances JavaScript development with improved speed, Node.js compatibility, and a streamlined toolchain.</description>
<category>Tech News</category>
<content:encoded><![CDATA[<p>Modern JavaScript development often feels bogged down by slow build times and a fragmented ecosystem of tools. Developers frequently juggle multiple command-line interfaces for running code, managing packages, and bundling assets. This overhead can significantly impact productivity and the overall developer experience.</p>
<h2 id="what-is-bun">What is Bun?</h2>
<p>Bun is an all-in-one JavaScript runtime, bundler, transpiler, and package manager built from scratch with a focus on speed. Developed by Jarred Sumner and his team, Bun aims to be a complete toolkit for JavaScript and TypeScript projects. It leverages the highly performant Zig programming language and the JavaScriptCore engine (used by Safari) to deliver incredibly fast execution and build times. Unlike Node.js, which traditionally requires separate tools like npm or yarn for package management and Webpack or Rollup for bundling, Bun integrates these functionalities into a single executable. This unified approach simplifies project setup and streamlines development workflows considerably.</p>
<h2 id="key-enhancements-in-bun-11">Key Enhancements in Bun 1.1</h2>
<p>Bun 1.1, released a few weeks ago, brought a wave of significant improvements, particularly focusing on stability, performance, and Node.js compatibility. These updates are crucial for Bun's adoption in production environments.</p>
<h3 id="improved-nodejs-compatibility">Improved Node.js Compatibility</h3>
<p>One of the most critical aspects of Bun's development is its pursuit of broad Node.js compatibility. Bun 1.1 made substantial strides in this area, achieving over 90% compatibility with Node.js's native modules and APIs. This means a wider range of existing npm packages and Node.js applications can run on Bun with fewer modifications. Specific improvements include better support for:</p>
<ul>
<li><strong><code>fs</code> module</strong>: Enhanced stability and compliance with various file system operations.</li>
<li><strong><code>path</code> module</strong>: More accurate path resolution consistent with Node.js.</li>
<li><strong><code>http</code> and <code>https</code> modules</strong>: Improved server and client-side networking capabilities, reducing edge cases.</li>
<li><strong>Environment Variables</strong>: More reliable handling of <code>process.env</code> and related functionalities.</li>
</ul>
<p>These compatibility gains make it easier for developers to migrate existing projects or start new ones with Bun without worrying about inconsistencies.</p>
<h3 id="performance-boosts">Performance Boosts</h3>
<p>Speed is Bun's core promise, and 1.1 delivers further on this commitment:</p>
<ul>
<li><strong>Faster <code>bun install</code></strong>: The package installer continues to be significantly quicker than npm or yarn. Bun 1.1 optimized internal caching and parallel processing, leading to even faster dependency installations, often completing in milliseconds for typical projects.</li>
<li><strong>Optimized <code>bun run</code></strong>: Executing scripts via <code>bun run</code> saw performance improvements, especially for complex scripts involving multiple steps.</li>
<li><strong>More Efficient Module Resolution</strong>: The internal module resolution logic was refined, speeding up how Bun locates and loads dependencies.</li>
</ul>
<h3 id="windows-support">Windows Support</h3>
<p>Bun 1.1 marked a major milestone with significantly improved Windows support. While still actively developing, the release made it possible for Windows users to leverage Bun's features with greater stability and fewer workarounds. This broadens Bun's reach and makes it a viable option for a larger developer base.</p>
<h3 id="websocket-stability">WebSocket Stability</h3>
<p>For real-time applications, WebSocket stability is paramount. Bun 1.1 addressed several known issues with its WebSocket implementation, making it more robust and reliable for building chat applications, collaborative tools, and other interactive services.</p>
<h2 id="practical-benefits-for-developers">Practical Benefits for Developers</h2>
<p>The enhancements in Bun 1.1 translate directly into tangible benefits for developers:</p>
<ul>
<li><strong>Simplified Toolchain</strong>: Replace npm/yarn, Node.js, and a bundler like Webpack with a single, fast tool. This reduces configuration complexity and dependency management.</li>
<li><strong>Reduced Build Times</strong>: Enjoy lightning-fast dependency installs and script executions, leading to quicker iteration cycles and less waiting.</li>
<li><strong>Better Developer Experience (DX)</strong>: A consistent, high-performance toolchain reduces frustration and allows developers to focus more on writing code.</li>
<li><strong>Faster Applications</strong>: Bun's native performance can also translate into faster server-side applications and development servers.</li>
</ul>
<h2 id="getting-started-with-bun-11">Getting Started with Bun 1.1</h2>
<p>Starting with Bun is straightforward. If you are on macOS or Linux, you can install it with a simple shell command:</p>
<pre tabindex="0"><code class="hljs language-bash">curl -fsSL https://bun.sh/install | bash
</code></pre>
<p>For Windows users, Bun can be installed via PowerShell or WSL (Windows Subsystem for Linux). Once installed, you can use it immediately.</p>
<p><strong>Example: Running a JavaScript file</strong></p>
<p>Instead of <code>node index.js</code>:</p>
<pre tabindex="0"><code class="hljs language-bash">bun run index.js
</code></pre>
<p><strong>Example: Installing dependencies</strong></p>
<p>Instead of <code>npm install</code> or <code>yarn install</code>:</p>
<pre tabindex="0"><code class="hljs language-bash">bun install
</code></pre>
<p><strong>Example: Bundling a project</strong></p>
<pre tabindex="0"><code class="hljs language-bash">bun build src/index.ts --outdir ./build
</code></pre>
<p>This simple, unified interface greatly simplifies common development tasks.</p>
<h2 id="the-road-ahead-for-bun">The Road Ahead for Bun</h2>
<p>The Bun project continues its rapid development, with ongoing efforts to achieve 100% Node.js compatibility, further performance optimizations, and new features. The community's feedback plays a vital role in shaping its future, making it a dynamic and exciting tool to watch in the JavaScript ecosystem.</p>
<h2 id="takeaway">Takeaway</h2>
<p>Bun 1.1 represents a significant step forward for this ambitious JavaScript runtime. Its focus on speed, compatibility, and an all-in-one approach offers a compelling alternative to traditional JavaScript toolchains. Developers looking to accelerate their workflows and simplify their development environment should seriously consider integrating Bun into their projects.</p>]]></content:encoded>
</item>
<item>
<title>Simplify Promise Handling with Promise.withResolvers</title>
<link>https://2haas.com/blog/simplify-promise-with-resolvers/</link>
<guid isPermaLink="true">https://2haas.com/blog/simplify-promise-with-resolvers/</guid>
<pubDate>Tue, 13 Oct 2026 13:14:45 GMT</pubDate>
<description>Learn how the new Promise.withResolvers static method streamlines common asynchronous patterns in JavaScript, reducing boilerplate and improving code clarity.</description>
<category>Code Snippets</category>
<content:encoded><![CDATA[<p>Many asynchronous operations in JavaScript rely on Promises. Developers often face challenges when needing to create a Promise and resolve or reject it externally, outside of its immediate constructor scope. This scenario commonly arises in event-driven programming, third-party API wrappers, or custom asynchronous utilities, leading to verbose and less intuitive code.</p>
<h2 id="the-traditional-deferred-pattern">The Traditional "Deferred" Pattern</h2>
<p>Before the introduction of <code>Promise.withResolvers</code>, a common way to achieve external promise control was through what's often called the "deferred" pattern. This involved declaring the <code>resolve</code> and <code>reject</code> functions for a new Promise outside of its constructor, making them accessible to other parts of your code. While functional, this approach introduced boilerplate and could sometimes lead to less readable code.</p>
<p>Consider this typical example:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">let</span> externalResolve;
<span class="hljs-keyword">let</span> externalReject;

<span class="hljs-keyword">const</span> myDeferredPromise = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =></span> {
  externalResolve = resolve;
  externalReject = reject;
});

<span class="hljs-comment">// Later, somewhere else in the code:</span>
<span class="hljs-comment">// externalResolve('Data received!');</span>
<span class="hljs-comment">// externalReject(new Error('Operation failed!'));</span>
</code></pre>
<p>This pattern works, but it requires temporary variables that pollute the scope where the promise is defined. It adds verbosity and makes the intent slightly less direct than it could be.</p>
<h2 id="introducing-promisewithresolvers">Introducing Promise.withResolvers</h2>
<p><code>Promise.withResolvers</code> is a new static method, currently at Stage 3 in the TC39 process and available in modern JavaScript environments (like Node.js 22+ and recent browser versions). It significantly simplifies the "deferred" pattern by returning an object containing the promise itself, along with its <code>resolve</code> and <code>reject</code> functions. This eliminates the need for external variable declarations.</p>
<p>Its signature is straightforward:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">const</span> { promise, resolve, reject } = <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">withResolvers</span>();
</code></pre>
<p>Here's how you would achieve the same functionality as the previous example with <code>Promise.withResolvers</code>:</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">const</span> { promise, resolve, reject } = <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">withResolvers</span>();

<span class="hljs-comment">// Now, 'promise' is the Promise object</span>
<span class="hljs-comment">// 'resolve' is the function to fulfill 'promise'</span>
<span class="hljs-comment">// 'reject' is the function to reject 'promise'</span>

<span class="hljs-comment">// Later, somewhere else in the code:</span>
<span class="hljs-comment">// resolve('Data received!');</span>
<span class="hljs-comment">// reject(new Error('Operation failed!'));</span>
</code></pre>
<p>This approach is much cleaner and more concise. It directly provides the necessary components without intermediate variables, enhancing code readability and reducing potential errors from managing scope.</p>
<h2 id="practical-use-cases">Practical Use Cases</h2>
<p><code>Promise.withResolvers</code> shines in scenarios where you need to integrate promise-based asynchronous flow with callback-based APIs or event-driven systems.</p>
<h3 id="1-wrapping-event-listeners">1. Wrapping Event Listeners</h3>
<p>One common application is to convert a one-time event into a promise. Imagine you want to wait for a specific DOM event to occur and then continue your asynchronous flow.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">waitForEvent</span>(<span class="hljs-params">element, eventName</span>) {
  <span class="hljs-keyword">const</span> { promise, resolve } = <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">withResolvers</span>();

  element.<span class="hljs-title function_">addEventListener</span>(eventName, <span class="hljs-function">(<span class="hljs-params">event</span>) =></span> {
    <span class="hljs-title function_">resolve</span>(event); <span class="hljs-comment">// Resolve the promise with the event object</span>
  }, { <span class="hljs-attr">once</span>: <span class="hljs-literal">true</span> }); <span class="hljs-comment">// Ensure the listener is removed after first execution</span>

  <span class="hljs-keyword">return</span> promise;
}

<span class="hljs-comment">// Example usage:</span>
<span class="hljs-comment">// async function run() {</span>
<span class="hljs-comment">//   const button = document.getElementById('myButton');</span>
<span class="hljs-comment">//   console.log('Waiting for button click...');</span>
<span class="hljs-comment">//   const clickEvent = await waitForEvent(button, 'click');</span>
<span class="hljs-comment">//   console.log('Button clicked!', clickEvent.target.id);</span>
<span class="hljs-comment">// }</span>
<span class="hljs-comment">// run();</span>
</code></pre>
<h3 id="2-streamlining-callback-based-api-wrappers">2. Streamlining Callback-based API Wrappers</h3>
<p>When dealing with older APIs that solely rely on callbacks, <code>Promise.withResolvers</code> can simplify the conversion into a modern promise-based interface.</p>
<pre tabindex="0"><code class="hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">promisifyCallbackAPI</span>(<span class="hljs-params">callbackBasedFunction, ...args</span>) {
  <span class="hljs-keyword">const</span> { promise, resolve, reject } = <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">withResolvers</span>();

  <span class="hljs-title function_">callbackBasedFunction</span>(...args, <span class="hljs-function">(<span class="hljs-params">error, data</span>) =></span> {
    <span class="hljs-keyword">if</span> (error) {
      <span class="hljs-title function_">reject</span>(error);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-title function_">resolve</span>(data);
    }
  });

  <span class="hljs-keyword">return</span> promise;
}

<span class="hljs-comment">// Imagine an oldNodeStyleReadFunction(filePath, callback) from an older library</span>
<span class="hljs-comment">// async function readFilePromise(filePath) {</span>
<span class="hljs-comment">//   return promisifyCallbackAPI(oldNodeStyleReadFunction, filePath);</span>
<span class="hljs-comment">// }</span>
</code></pre>
<h2 id="benefits-and-considerations">Benefits and Considerations</h2>
<h3 id="benefits">Benefits</h3>
<ul>
<li><strong>Reduced Boilerplate:</strong> Eliminates the need for externally declared <code>resolve</code> and <code>reject</code> variables.</li>
<li><strong>Improved Readability:</strong> The intent of creating a promise whose resolution is controlled externally becomes clearer.</li>
<li><strong>Better Ergonomics:</strong> Provides a more direct and less error-prone way to manage promise lifecycle when external control is needed.</li>
<li><strong>Cleaner Error Handling:</strong> The <code>reject</code> function is readily available for handling errors from the external source.</li>
</ul>
<h3 id="considerations">Considerations</h3>
<ul>
<li><strong>Environment Support:</strong> As <code>Promise.withResolvers</code> is a relatively new addition (ES2024 / Stage 3), ensure that your target environments (browsers, Node.js versions) support it. You might need polyfills or transpilation for older environments.</li>
<li><strong>Not a Replacement for <code>async/await</code>:</strong> This method is for <em>creating</em> promises with external control, not for consuming them. For sequential asynchronous operations or error handling, <code>async/await</code> and <code>try/catch</code> remain the standard.</li>
<li><strong>Single Resolution:</strong> Like all promises, a promise created with <code>Promise.withResolvers</code> can only be resolved or rejected once. Subsequent calls to <code>resolve</code> or <code>reject</code> will have no effect.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p><code>Promise.withResolvers</code> offers a welcome improvement for a specific but common asynchronous pattern in JavaScript development. By streamlining the creation of promises whose fate is determined externally, it helps developers write cleaner, more maintainable, and less verbose code. Embrace this new feature to make your event-driven and callback-integrated asynchronous logic more elegant.</p>
<h3 id="takeaway-checklist">Takeaway Checklist:</h3>
<ul>
<li>Use <code>Promise.withResolvers</code> to create promises whose resolution or rejection needs external control.</li>
<li>Reduce boilerplate code typically associated with the "deferred" pattern.</li>
<li>Enhance the readability and maintainability of asynchronous code.</li>
<li>Verify target environment support before widespread adoption.</li>
</ul>]]></content:encoded>
</item>
<item>
<title>Enhance Web Security with Content Security Policy</title>
<link>https://2haas.com/blog/content-security-policy-guide/</link>
<guid isPermaLink="true">https://2haas.com/blog/content-security-policy-guide/</guid>
<pubDate>Mon, 12 Oct 2026 13:15:15 GMT</pubDate>
<description>Learn how to implement a Content Security Policy (CSP) to protect your web applications from common attacks like XSS and data injection.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>Web applications are constantly targeted by various security threats, most notably Cross-Site Scripting (XSS) and data injection attacks. These vulnerabilities can allow attackers to inject malicious code into your website, compromising user data, session tokens, and overall application integrity. Traditional server-side defenses, while crucial, often fall short in preventing client-side attacks once a vulnerability has been exploited. This is where Content Security Policy (CSP) provides a powerful, client-side layer of defense.</p>
<h2 id="what-is-content-security-policy-csp">What is Content Security Policy (CSP)?</h2>
<p>Content Security Policy (CSP) is a web security standard that helps prevent various types of code injection attacks, including XSS. It does this by specifying trusted content sources that the browser is allowed to load and execute for a given page. Essentially, you provide a whitelist of domains and behaviors, and the browser will block anything that violates these rules. This dramatically reduces the attack surface for malicious content.</p>
<p>CSP works as a declaration that tells the browser, "Only load scripts from these domains, only apply styles from these sources, and so on." If the browser attempts to load content from a source not explicitly allowed by the CSP, it will block the request and optionally report the violation. This control applies to scripts, stylesheets, images, media, fonts, frames, web workers, and more.</p>
<h2 id="how-csp-works">How CSP Works</h2>
<p>CSP is primarily enforced by the browser based on an HTTP response header or a HTML meta tag. The most common and recommended approach is to include a <code>Content-Security-Policy</code> HTTP header in your server's responses. When a browser receives this header, it parses the policy directives and enforces them for all resources requested by that page. If a resource request violates the policy, the browser typically blocks it and may send a violation report to a specified URI.</p>
<p>Each policy consists of one or more directives, each governing a specific type of resource or behavior. For example, the <code>script-src</code> directive controls JavaScript sources, <code>style-src</code> controls CSS sources, and <code>img-src</code> controls image sources. The <code>default-src</code> directive acts as a fallback for any resource type not explicitly covered by another directive.</p>
<h2 id="implementing-a-basic-csp">Implementing a Basic CSP</h2>
<p>To implement a CSP, you typically configure your web server or application framework to send the <code>Content-Security-Policy</code> HTTP header. Let's start with a restrictive baseline and then add exceptions.</p>
<ol>
<li>
<p><strong>Start with a strict <code>default-src</code>:</strong>
This directive specifies the default policy for fetching resources. Setting it to <code>'self'</code> is a strong starting point, meaning only resources from the current origin are allowed.</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy</span><span class="hljs-punctuation">: </span>default-src 'self';
</code></pre>
</li>
<li>
<p><strong>Add specific directives for common resources:</strong>
You will likely need to allow resources from other trusted domains, such as CDNs, analytics scripts, or embedded content. Here's an example allowing Google Analytics and a custom CDN:</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy</span><span class="hljs-punctuation">: </span>default-src 'self'; script-src 'self' https://www.google-analytics.com https://cdn.example.com; style-src 'self' https://cdn.example.com; img-src 'self' data: https://cdn.example.com;
</code></pre>
<ul>
<li><code>script-src</code>: Allows JavaScript from your origin, Google Analytics, and <code>cdn.example.com</code>.</li>
<li><code>style-src</code>: Allows stylesheets from your origin and <code>cdn.example.com</code>.</li>
<li><code>img-src</code>: Allows images from your origin, <code>data:</code> URIs (for inline images), and <code>cdn.example.com</code>.</li>
</ul>
</li>
<li>
<p><strong>Consider reporting violations:</strong>
Use the <code>report-uri</code> or <code>report-to</code> directive to send violation reports to a specified endpoint. This helps you identify and fix CSP issues without immediately blocking content.</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy</span><span class="hljs-punctuation">: </span>default-src 'self'; script-src 'self' https://www.google-analytics.com; report-uri /csp-violation-report-endpoint;
</code></pre>
<p>Initially, you might want to use <code>Content-Security-Policy-Report-Only</code> header instead of <code>Content-Security-Policy</code>. This allows you to test your policy and collect reports without actually enforcing blocks, preventing potential breakage of your site.</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy-Report-Only</span><span class="hljs-punctuation">: </span>default-src 'self'; script-src 'self' https://www.google-analytics.com; report-uri /csp-violation-report-endpoint;
</code></pre>
</li>
</ol>
<h2 id="refining-your-csp">Refining Your CSP</h2>
<p>Creating an effective CSP often requires careful refinement. Here are key considerations:</p>
<ul>
<li>
<p><strong>Inline Scripts and Styles:</strong> Directives like <code>script-src</code> and <code>style-src</code> inherently block inline JavaScript (<code>&#x3C;script>...&#x3C;/script></code>) and inline CSS (<code>&#x3C;style>...&#x3C;/style></code>, <code>style="..."</code>). To allow specific inline content, you must use cryptographically secure nonces or hashes.</p>
<ul>
<li>
<p><strong>Nonces:</strong> A unique, unguessable value generated on each page load. You add the nonce to both the CSP header and the inline script/style tag.</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy</span><span class="hljs-punctuation">: </span>script-src 'nonce-RANDOM_VALUE';
</code></pre>
<pre tabindex="0"><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">script</span> <span class="hljs-attr">nonce</span>=<span class="hljs-string">"RANDOM_VALUE"</span>></span><span class="javascript"> <span class="hljs-comment">/* your inline script */</span> </span><span class="hljs-tag">&#x3C;/<span class="hljs-name">script</span>></span>
</code></pre>
</li>
<li>
<p><strong>Hashes:</strong> A SHA256, SHA384, or SHA512 hash of the inline script/style content. The browser calculates the hash and compares it to the policy.</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy</span><span class="hljs-punctuation">: </span>script-src 'sha256-BASE64_ENCODED_HASH';
</code></pre>
<pre tabindex="0"><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">script</span>></span><span class="javascript"> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Hello, world.'</span>); </span><span class="hljs-tag">&#x3C;/<span class="hljs-name">script</span>></span>
</code></pre>
<p><em>The hash for <code>console.log('Hello, world.');</code> is <code>sha256-DQyJp5yH7sUfXq8t/Iq6Pj0eJ7bL/Qx/Yj+y/1+N/5M=</code></em></p>
</li>
</ul>
</li>
<li>
<p><strong><code>'unsafe-inline'</code> and <code>'unsafe-eval'</code>:</strong> Avoid these directives if possible. <code>'unsafe-inline'</code> allows all inline scripts/styles, negating much of CSP's XSS protection. <code>'unsafe-eval'</code> allows JavaScript <code>eval()</code> and similar functions, which can also be exploited. Use nonces or hashes for specific inline content. If you absolutely must use them, be aware of the security implications.</p>
</li>
<li>
<p><strong>Frame Control:</strong> Use <code>frame-src</code> for <code>&#x3C;iframe></code>, <code>&#x3C;frame></code>, <code>&#x3C;object></code>, <code>&#x3C;embed></code>, and <code>&#x3C;applet></code>. Use <code>frame-ancestors</code> to control which sources can embed your page (e.g., in an <code>&#x3C;iframe></code>).</p>
<pre tabindex="0"><code class="hljs language-http"><span class="hljs-attribute">Content-Security-Policy</span><span class="hljs-punctuation">: </span>frame-src 'self' https://trusted-embed.com; frame-ancestors 'self' https://your-parent-domain.com;
</code></pre>
</li>
<li>
<p><strong>Workers:</strong> For Web Workers or Service Workers, use <code>worker-src</code>.</p>
</li>
</ul>
<h2 id="common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</h2>
<p>Implementing CSP can be tricky, especially on existing large applications. Here are some tips:</p>
<ol>
<li><strong>Incremental Rollout:</strong> Start with a <code>Content-Security-Policy-Report-Only</code> header. Monitor violation reports for a period. This allows you to identify all necessary directives before enforcing the policy, preventing site breakage.</li>
<li><strong>Be Specific:</strong> Always try to specify exact domains rather than broad wildcards (e.g., <code>*.example.com</code>).</li>
<li><strong>Review Third-Party Scripts:</strong> Understand what every third-party script on your site does and where it fetches resources from. They often require additional CSP directives.</li>
<li><strong>Avoid Hash Collisions:</strong> When using hashes for inline scripts, ensure the exact script content matches the hash. Even a single character change invalidates the hash.</li>
<li><strong>Performance:</strong> While CSP itself has minimal performance impact, an overly complex policy might slightly increase parsing time. Keep it as streamlined as possible.</li>
<li><strong>Browser Support:</strong> CSP Level 2 and Level 3 offer more features and wider browser support. Most modern browsers support core CSP directives. Always provide a robust fallback or progressive enhancement for users on older browsers.</li>
</ol>
<h2 id="takeaway-and-checklist">Takeaway and Checklist</h2>
<p>Content Security Policy is a critical security layer for modern web applications, effectively mitigating XSS and other injection attacks by whitelisting trusted content sources. Proper implementation requires careful planning and testing, but the security benefits are substantial.</p>
<p><strong>CSP Implementation Checklist:</strong></p>
<ul>
<li>Start with <code>Content-Security-Policy-Report-Only</code> to monitor violations.</li>
<li>Set a strict <code>default-src 'self'</code> directive.</li>
<li>Identify and whitelist all necessary script, style, image, and other resource sources.</li>
<li>Replace <code>'unsafe-inline'</code> and <code>'unsafe-eval'</code> with nonces or hashes where absolutely necessary.</li>
<li>Configure <code>report-uri</code> or <code>report-to</code> for violation reporting.</li>
<li>Test your policy thoroughly across different browsers and user flows.</li>
<li>Switch to <code>Content-Security-Policy</code> once you are confident the policy does not break your site.</li>
</ul>]]></content:encoded>
</item>
<item>
<title>GitHub Copilot Workspace Transforms Developer Workflow</title>
<link>https://2haas.com/blog/github-copilot-workspace-workflow/</link>
<guid isPermaLink="true">https://2haas.com/blog/github-copilot-workspace-workflow/</guid>
<pubDate>Sun, 11 Oct 2026 19:13:50 GMT</pubDate>
<description>Explore GitHub Copilot Workspace, a new AI-powered development environment that streamlines coding from issue to pull request. Learn its features and impact.</description>
<category>Tech News</category>
<content:encoded><![CDATA[<p>Software development is often a fragmented process. Developers frequently spend considerable time on non-coding tasks such as setting up development environments, understanding complex codebases, and switching contexts between different parts of a project. This overhead can significantly slow down progress and create barriers for new team members trying to contribute. The focus shifts from solving problems to managing the mechanics of the development workflow.</p>
<h2 id="what-is-github-copilot-workspace">What is GitHub Copilot Workspace?</h2>
<p>GitHub Copilot Workspace is an innovative, AI-powered development environment designed to streamline the entire software development lifecycle, from issue to pull request. Announced as a major leap forward, it extends the capabilities of GitHub Copilot beyond just code suggestions. Instead, it acts as an agentic AI partner that can comprehend issues, generate an execution plan, set up a development environment, and even implement the code changes required. It is an integrated space where the AI understands the context of a repository, an issue, or a feature request, and then takes proactive steps to assist the developer.</p>
<h2 id="the-integrated-workflow-from-idea-to-pr">The Integrated Workflow: From Idea to PR</h2>
<p>Copilot Workspace aims to collapse multiple development stages into a more seamless, AI-assisted flow. The core workflow typically follows these steps:</p>
<ol>
<li><strong>Issue Analysis</strong>: A developer starts by identifying an issue, a bug, or a feature request within a GitHub repository. Copilot Workspace begins by analyzing the problem statement, considering the codebase's context.</li>
<li><strong>Plan Generation</strong>: Based on the issue, the AI proposes a detailed plan of action. This plan outlines the necessary code changes, file modifications, and even potential test cases. Developers can review and refine this plan.</li>
<li><strong>Environment Setup</strong>: Once the plan is approved, Copilot Workspace automatically provisions a ready-to-use development environment. This environment includes all the necessary dependencies, configurations, and relevant files open for the task at hand, eliminating manual setup.</li>
<li><strong>Code Implementation</strong>: The AI then proceeds to write the actual code. It can generate new code, modify existing functions, and update configurations based on the agreed-upon plan and the context of the project.</li>
<li><strong>Review and Refine</strong>: Developers maintain full control throughout the process. They can review the generated code, make manual adjustments, run tests, and interactively debug within the workspace. The human remains in the loop, guiding the AI and ensuring code quality.</li>
<li><strong>Pull Request Creation</strong>: Once satisfied, the developer can submit a pull request directly from the workspace, complete with a detailed description generated by the AI summarizing the changes and their rationale.</li>
</ol>
<p>This structured approach ensures that developers can focus on high-level problem-solving and critical thinking, while the AI handles the repetitive and time-consuming tasks.</p>
<h2 id="key-features-and-components">Key Features and Components</h2>
<p>GitHub Copilot Workspace combines several powerful technologies to deliver its capabilities:</p>
<ul>
<li><strong>Agentic AI</strong>: Unlike simple code completion, Copilot Workspace employs an agentic AI system that understands goals and executes multi-step tasks autonomously.</li>
<li><strong>Deep Contextual Understanding</strong>: It leverages the entire GitHub repository as its context, including issues, pull requests, project history, and code structure, allowing for highly relevant and accurate suggestions.</li>
<li><strong>Integrated Development Environment (IDE)</strong>: The workspace itself provides a fully functional, cloud-based IDE where developers can view, edit, and test code. This ensures consistency across teams.</li>
<li><strong>Interactive Chat Interface</strong>: Developers communicate with the AI agent through a natural language chat, iteratively refining the plan and code.</li>
<li><strong>Version Control Integration</strong>: Seamless integration with Git and GitHub ensures that all changes are tracked, and pull requests are managed effectively.</li>
</ul>
<h2 id="benefits-for-developers-and-teams">Benefits for Developers and Teams</h2>
<p>The introduction of GitHub Copilot Workspace promises several transformative benefits:</p>
<ul>
<li><strong>Accelerated Development</strong>: By automating environment setup and initial code generation, development cycles are significantly shortened.</li>
<li><strong>Reduced Context Switching</strong>: Developers spend less time navigating different tools and setting up their local machines, allowing them to remain focused on the task.</li>
<li><strong>Enhanced Onboarding</strong>: New team members can become productive much faster, as the AI can guide them through understanding a new codebase and making their first contributions.</li>
<li><strong>Increased Consistency</strong>: Standardized environments and AI-generated plans can lead to more consistent coding practices across projects and teams.</li>
<li><strong>Greater Innovation</strong>: Freeing up developers from repetitive tasks allows them to dedicate more time to complex problem-solving and innovative solutions.</li>
</ul>
<h2 id="challenges-and-future-outlook">Challenges and Future Outlook</h2>
<p>While promising, the adoption of Copilot Workspace will also bring new considerations:</p>
<ul>
<li><strong>Trust and Verification</strong>: Developers will need to build trust in AI-generated code, necessitating thorough review and testing processes.</li>
<li><strong>Managing Complexity</strong>: As AI takes on more complex tasks, managing the interaction and ensuring the AI's understanding aligns with human intent will be crucial.</li>
<li><strong>Learning Curve</strong>: While designed to simplify, there will be an initial learning curve for developers to effectively leverage the new AI-powered workflow.</li>
</ul>
<p>GitHub Copilot Workspace represents a significant shift in how we approach software development. It moves beyond AI as a coding assistant to AI as a full development partner, orchestrating the entire workflow from problem identification to code delivery. This new paradigm could redefine productivity and collaboration in the developer community.</p>
<h2 id="takeaway">Takeaway</h2>
<p>GitHub Copilot Workspace aims to fundamentally change the developer experience by integrating AI throughout the development lifecycle. This could lead to faster feature delivery, improved team collaboration, and a more focused approach to problem-solving. Embracing such tools will be key for future-proofing development workflows.</p>]]></content:encoded>
</item>
<item>
<title>Mastering Responsive Layouts with CSS Container Queries</title>
<link>https://2haas.com/blog/css-container-queries-guide/</link>
<guid isPermaLink="true">https://2haas.com/blog/css-container-queries-guide/</guid>
<pubDate>Sun, 11 Oct 2026 19:09:59 GMT</pubDate>
<description>Learn how to implement CSS Container Queries for truly responsive, component-based layouts. This guide covers setup, syntax, and practical examples.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>Web developers often face challenges creating truly adaptive user interfaces. While media queries allow designs to respond to the overall viewport size, they fall short when individual components need to adjust based on the space available within their parent containers. This limitation often leads to complex, hard-to-maintain CSS and reduces the reusability of UI components across different parts of an application.</p>
<h2 id="understanding-css-container-queries">Understanding CSS Container Queries</h2>
<p>CSS Container Queries bring a revolutionary approach to responsive web design. Instead of reacting to the browser's viewport, components can now react to the size of their direct parent container. This means you can design a component, like a card, to behave differently when it's placed in a narrow sidebar versus a wide main content area, without needing specific media queries for each scenario. This capability makes components much more modular and reusable, aligning perfectly with modern component-driven development practices.</p>
<h2 id="setting-up-a-container-context">Setting Up a Container Context</h2>
<p>To start using container queries, you first need to define a containing element. This element will act as the context for its child elements' container queries. You do this by applying the <code>container-type</code> property to the parent element.</p>
<ol>
<li><strong>Identify the Parent</strong>: Choose the HTML element that will enclose your responsive component.</li>
<li><strong>Define Container Type</strong>: Add the <code>container-type</code> CSS property to this parent.
<ul>
<li><code>inline-size</code>: The container queries will react to the parent's width (the most common use case).</li>
<li><code>size</code>: The container queries will react to both the parent's width and height.</li>
<li><code>normal</code>: This is the default and does not establish a query container.</li>
</ul>
</li>
</ol>
<p>It is also good practice to give your container a <code>container-name</code>. This allows you to target specific containers if you have nested ones, making your queries clearer and preventing conflicts.</p>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-selector-class">.card-container</span> {
  <span class="hljs-attribute">container-type</span>: inline-size;
  <span class="hljs-attribute">container-name</span>: myCard; <span class="hljs-comment">/* Optional but recommended */</span>
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>;
}
</code></pre>
<h2 id="writing-your-first-container-query">Writing Your First Container Query</h2>
<p>Once you have a container context set up, you can write <code>@container</code> rules inside your CSS. These rules are similar in syntax to <code>@media</code> rules but target the named container or the nearest ancestor container.</p>
<ol>
<li><strong>Target the Container</strong>: Use the <code>@container</code> at-rule. You can specify a name if you used <code>container-name</code>.</li>
<li><strong>Define Conditions</strong>: Inside <code>@container</code>, specify conditions using <code>min-width</code>, <code>max-width</code>, <code>min-height</code>, <code>max-height</code>, or other logical operators (and, or, not). These conditions refer to the dimensions of the <em>container</em> itself, not the viewport.</li>
<li><strong>Apply Styles</strong>: Place the CSS rules that should apply when the conditions are met.</li>
</ol>
<p>Here is an example for a card component:</p>
<pre tabindex="0"><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-container"</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>></span>
    <span class="hljs-tag">&#x3C;<span class="hljs-name">h3</span>></span>Product Title<span class="hljs-tag">&#x3C;/<span class="hljs-name">h3</span>></span>
    <span class="hljs-tag">&#x3C;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"product.jpg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Product Image"</span>></span>
    <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span>></span>Short description of the product.<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>
    <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span>></span>Add to Cart<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
</code></pre>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#eee</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">4px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.1</span>);
}

<span class="hljs-keyword">@container</span> myCard (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">400px</span>) {
  <span class="hljs-selector-class">.card</span> {
    <span class="hljs-attribute">flex-direction</span>: row;
    <span class="hljs-attribute">align-items</span>: center;
  }
  <span class="hljs-selector-class">.card</span> <span class="hljs-selector-tag">img</span> {
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">150px</span>;
    <span class="hljs-attribute">height</span>: auto;
  }
}

<span class="hljs-keyword">@container</span> myCard (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">700px</span>) {
  <span class="hljs-selector-class">.card</span> {
    <span class="hljs-attribute">grid-template-columns</span>: auto <span class="hljs-number">1</span>fr auto; <span class="hljs-comment">/* Example for a more complex layout */</span>
    <span class="hljs-attribute">display</span>: grid;
    <span class="hljs-attribute">align-items</span>: center;
  }
  <span class="hljs-selector-class">.card</span> <span class="hljs-selector-tag">h3</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
  }
}
</code></pre>
<p>In this example, the <code>.card</code> component will initially stack its content vertically. If its parent <code>.card-container</code> becomes at least 400px wide, the card's layout switches to a horizontal row. If the parent grows to 700px, it adapts to a grid layout. Notice how the component itself defines its responsive behavior based on its environment.</p>
<h2 id="common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</h2>
<p>While powerful, container queries require careful implementation to maximize their benefits.</p>
<ul>
<li><strong>Avoid Circular Dependencies</strong>: Do not query a container based on properties that are also being set by the query itself, as this can lead to infinite loops or unpredictable behavior. For example, if a container query changes the width of its direct child, and that child is also a container being queried, ensure the conditions are clear.</li>
<li><strong>Performance Considerations</strong>: Like any CSS feature, overuse or overly complex queries can impact performance. Focus on querying relevant dimensions (<code>inline-size</code> is often sufficient) and avoid querying <code>size</code> unless necessary.</li>
<li><strong>Browser Support</strong>: Container queries have excellent modern browser support. However, for older browsers, you might need a fallback or progressive enhancement strategy. Features like <code>@supports</code> can help you provide alternative styles.</li>
<li><strong>Logical Properties</strong>: Combine container queries with CSS logical properties (like <code>block-size</code> instead of <code>height</code>) for even more flexible and internationalized designs.</li>
</ul>
<h2 id="impact-on-component-reusability">Impact on Component Reusability</h2>
<p>The greatest advantage of CSS Container Queries lies in their ability to decouple component styling from global layout concerns. Developers can build independent, self-contained components that carry their own responsiveness. This significantly boosts reusability, reduces the amount of context-specific CSS needed, and makes designing complex, adaptable user interfaces far more manageable. Imagine a design system where every component inherently knows how to adjust itself, no matter where it's placed. Container queries make this a reality.</p>
<h2 id="takeaway">Takeaway</h2>
<p>CSS Container Queries represent a major leap forward for responsive web design. By enabling element-level responsiveness, they empower developers to build more modular, reusable, and robust UI components. Start integrating them into your workflow to simplify complex layouts and enhance the adaptability of your web applications.</p>]]></content:encoded>
</item>
<item>
<title>Async error handling in JavaScript: try/catch done right</title>
<link>https://2haas.com/blog/async-error-handling-javascript/</link>
<guid isPermaLink="true">https://2haas.com/blog/async-error-handling-javascript/</guid>
<pubDate>Sun, 11 Oct 2026 10:20:12 GMT</pubDate>
<description>Unhandled promise rejections crash Node.js services and leave browser apps quietly broken. Catch async errors with try/catch, .catch and a global safety net.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>Asynchronous code fails differently from synchronous code. An error thrown inside a promise does not travel up the call stack. It becomes a rejected promise, and if nothing handles that rejection, a browser app keeps running in a broken state while a Node.js process exits.</p>
<p>Most of these failures come from three small habits. Fix them and async code becomes predictable.</p>
<h2 id="why-trycatch-sometimes-misses-the-error">Why try/catch sometimes misses the error</h2>
<p><code>try/catch</code> only sees a rejection when the promise is awaited inside the <code>try</code> block.</p>
<pre tabindex="0"><code class="hljs language-js"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">loadUser</span>(<span class="hljs-params">id</span>) {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-title function_">fetchUser</span>(id); <span class="hljs-comment">// not awaited, so a rejection escapes the try block</span>
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(err); <span class="hljs-comment">// never runs</span>
  }
}
</code></pre>
<p>Add <code>await</code> and the same <code>catch</code> block starts working. The same rule applies to <code>return fetchUser(id)</code>: write <code>return await fetchUser(id)</code> when you want the surrounding <code>try</code> to handle the failure.</p>
<h2 id="the-pattern-for-asyncawait">The pattern for async/await</h2>
<pre tabindex="0"><code class="hljs language-js"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">loadUser</span>(<span class="hljs-params">id</span>) {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">`/api/users/<span class="hljs-subst">${id}</span>`</span>);
    <span class="hljs-keyword">if</span> (!res.<span class="hljs-property">ok</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`Request failed with status <span class="hljs-subst">${res.status}</span>`</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> res.<span class="hljs-title function_">json</span>();
  } <span class="hljs-keyword">catch</span> (err) {
    logger.<span class="hljs-title function_">error</span>({ err, id }, <span class="hljs-string">"Could not load user"</span>);
    <span class="hljs-keyword">throw</span> err; <span class="hljs-comment">// let the caller decide what the user sees</span>
  }
}
</code></pre>
<p>Two details matter here:</p>
<ul>
<li><code>fetch</code> only rejects on a network failure. A 404 or a 500 is a normal response, so check <code>res.ok</code> yourself.</li>
<li>Catching an error is not the same as handling it. Either recover with a sensible fallback or rethrow. Swallowing the error hides the bug.</li>
</ul>
<h2 id="promise-chains-end-with-catch">Promise chains end with .catch</h2>
<p>When you do not await a promise, finish the chain with <code>.catch</code>.</p>
<pre tabindex="0"><code class="hljs language-js"><span class="hljs-title function_">saveDraft</span>(draft).<span class="hljs-title function_">then</span>(showSaved).<span class="hljs-title function_">catch</span>(showSaveError);
</code></pre>
<p>This is the fix for fire-and-forget calls such as analytics events or background saves.</p>
<h2 id="add-a-global-safety-net">Add a global safety net</h2>
<p>A last line of defence tells you about the rejections you missed.</p>
<pre tabindex="0"><code class="hljs language-js"><span class="hljs-comment">// Browser</span>
<span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">"unhandledrejection"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =></span> {
  <span class="hljs-title function_">reportError</span>(event.<span class="hljs-property">reason</span>);
});

<span class="hljs-comment">// Node.js</span>
process.<span class="hljs-title function_">on</span>(<span class="hljs-string">"unhandledRejection"</span>, <span class="hljs-function">(<span class="hljs-params">reason</span>) =></span> {
  logger.<span class="hljs-title function_">fatal</span>({ reason }, <span class="hljs-string">"Unhandled rejection"</span>);
  process.<span class="hljs-title function_">exit</span>(<span class="hljs-number">1</span>);
});
</code></pre>
<p>Treat these as alarms. If they fire, there is a missing <code>try/catch</code> or <code>.catch</code> somewhere, and that is the real fix.</p>
<h2 id="a-short-checklist">A short checklist</h2>
<ol>
<li>Every <code>await</code> that can fail sits inside a <code>try</code> block, or the caller handles it.</li>
<li>Every promise you do not await ends in <code>.catch</code>.</li>
<li>HTTP responses are checked with <code>res.ok</code>.</li>
<li>Errors are logged once, with context, in one place.</li>
<li>The user sees what happened and what to do next.</li>
</ol>
<p>When several requests run together and some may fail, <a href="https://2haas.com/blog/promise-allsettled/">Promise.allSettled</a> keeps the successful results instead of throwing them away.</p>]]></content:encoded>
</item>
<item>
<title>Reduce Docker image size with multi-stage builds</title>
<link>https://2haas.com/blog/reduce-docker-image-size/</link>
<guid isPermaLink="true">https://2haas.com/blog/reduce-docker-image-size/</guid>
<pubDate>Fri, 25 Sep 2026 22:19:08 GMT</pubDate>
<description>Large Docker images slow down every build, push and deploy. Multi-stage builds, slim base images and a .dockerignore file can cut an image to a fraction of its size.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>A Docker image holds everything a container needs: code, runtime, libraries and system tools. It is easy to let that grow past a gigabyte without noticing. The cost shows up everywhere: slower CI runs, slower deploys, higher registry bills and a larger surface for security issues.</p>
<p>Four changes remove most of the weight.</p>
<h2 id="1-use-a-multi-stage-build">1. Use a multi-stage build</h2>
<p>Compilers, dev dependencies and build caches are needed to build the app, not to run it. A multi-stage build keeps them in a first stage and copies only the result into a clean final image.</p>
<pre tabindex="0"><code class="hljs language-dockerfile"><span class="hljs-comment"># Stage 1: build</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">22</span> AS build
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>

<span class="hljs-comment"># Stage 2: run</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">22</span>-alpine
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">ENV</span> NODE_ENV=production
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci --omit=dev</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=build /app/dist ./dist</span>
<span class="hljs-keyword">USER</span> node
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"dist/server.js"</span>]</span>
</code></pre>
<p>The final image contains the compiled output and production dependencies. The TypeScript compiler, test tools and source files stay behind in the build stage.</p>
<h2 id="2-pick-a-smaller-base-image">2. Pick a smaller base image</h2>
<p>The base image is often the largest layer.</p>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Base image</th>
<th>Rough size</th>
<th>Good for</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>node:22</code></td>
<td>About 1 GB</td>
<td>The build stage</td>
</tr>
<tr>
<td><code>node:22-slim</code></td>
<td>About 200 MB</td>
<td>Apps that need glibc</td>
</tr>
<tr>
<td><code>node:22-alpine</code></td>
<td>About 150 MB</td>
<td>Most Node.js services</td>
</tr>
<tr>
<td>Distroless</td>
<td>Smaller still</td>
<td>Locked down production images</td>
</tr>
</tbody>
</table></div>
<p>Alpine uses musl instead of glibc. Native modules sometimes need extra packages there, so test before you switch.</p>
<h2 id="3-add-a-dockerignore-file">3. Add a .dockerignore file</h2>
<p>Without it, <code>COPY . .</code> sends everything in the folder to the build, including <code>node_modules</code>, <code>.git</code> and local environment files.</p>
<pre tabindex="0"><code class="hljs language-text">node_modules
.git
dist
.env*
*.log
</code></pre>
<p>This makes builds faster and keeps secrets out of the image.</p>
<h2 id="4-clean-up-in-the-same-layer">4. Clean up in the same layer</h2>
<p>Each <code>RUN</code> instruction creates a layer. Files deleted in a later layer still take up space in the earlier one. Install and clean up in a single instruction.</p>
<pre tabindex="0"><code class="hljs language-dockerfile"><span class="hljs-keyword">RUN</span><span class="bash"> apt-get update \
  &#x26;&#x26; apt-get install -y --no-install-recommends curl \
  &#x26;&#x26; <span class="hljs-built_in">rm</span> -rf /var/lib/apt/lists/*</span>
</code></pre>
<h2 id="check-the-result">Check the result</h2>
<p>Run <code>docker images</code> to compare sizes before and after, and <code>docker history &#x3C;image></code> to see which layer is the heavy one. A Node.js service that started above 1 GB commonly lands under 200 MB after these steps.</p>
<p>Smaller images are half of the story. For the other half, see how to <a href="https://2haas.com/blog/speed-up-docker-builds/">speed up Docker builds with layer caching</a>.</p>]]></content:encoded>
</item>
<item>
<title>Refactoring with AI assistants: a safe workflow</title>
<link>https://2haas.com/blog/refactoring-with-ai-assistants/</link>
<guid isPermaLink="true">https://2haas.com/blog/refactoring-with-ai-assistants/</guid>
<pubDate>Thu, 24 Sep 2026 22:19:07 GMT</pubDate>
<description>AI assistants can untangle a long function in seconds, and introduce a subtle bug just as fast. This workflow keeps the speed and puts tests and review in charge.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>Old code is expensive to change. A 200 line function with nested conditions takes an afternoon to understand and another to restructure safely. AI coding assistants are good at exactly this kind of mechanical restructuring. They are also confident when they are wrong, so the workflow matters more than the tool.</p>
<h2 id="where-ai-helps-most">Where AI helps most</h2>
<ul>
<li>Splitting a long function into smaller, named ones.</li>
<li>Replacing nested <code>if</code> chains with early returns or a lookup table.</li>
<li>Renaming for clarity across a file.</li>
<li>Converting callbacks to <code>async/await</code>.</li>
<li>Writing the first draft of tests for code that has none.</li>
</ul>
<p>These are changes with a clear before and after, which makes them easy to verify.</p>
<h2 id="a-workflow-that-keeps-you-in-control">A workflow that keeps you in control</h2>
<ol>
<li><strong>Pin the behaviour with tests first.</strong> If the code has no tests, ask the assistant to write characterization tests that capture what the code does today, and run them.</li>
<li><strong>Give a narrow, specific instruction.</strong> "Extract the discount rules into separate functions. Do not change behaviour or the public signature" beats "clean this up".</li>
<li><strong>Work in small steps.</strong> One function or one concern per request. Small diffs are reviewable. Large ones get approved without being read.</li>
<li><strong>Read the diff like a pull request from a new colleague.</strong> Check edge cases, error handling and anything that was removed.</li>
<li><strong>Run the tests and commit.</strong> Then take the next step.</li>
</ol>
<h2 id="an-example">An example</h2>
<p>Take a function that calculates a customer discount with a dozen conditions for membership level, coupon and season. A good prompt looks like this:</p>
<pre tabindex="0"><code class="hljs language-text">Refactor calculateDiscount for readability.
- Extract each rule into its own small function.
- Keep the function signature and the results identical.
- Do not add dependencies.
Afterwards, list any behaviour you were unsure about.
</code></pre>
<p>The last line is the important one. It invites the assistant to point at the places where the original code was ambiguous, and those are the places to review by hand.</p>
<h2 id="what-to-watch-for">What to watch for</h2>
<ul>
<li><strong>Silent behaviour changes.</strong> A reordered condition can change the result for one edge case.</li>
<li><strong>Invented APIs.</strong> Check that every function and option it uses actually exists in your version of the library.</li>
<li><strong>Lost handling.</strong> Error branches and logging sometimes disappear during a "simplification".</li>
<li><strong>Sensitive code.</strong> Follow your company's policy on what can be sent to an external service.</li>
</ul>
<h2 id="the-result">The result</h2>
<p>Used this way, an assistant turns refactoring from a task people postpone into one they do as they go. The code gets cleaner in small, verified steps, and the tests you wrote to guard the change stay behind as a bonus.</p>
<p>Better instructions give better output. Our notes on <a href="https://2haas.com/blog/prompt-engineering-for-developers/">prompt engineering for developers</a> cover that side.</p>]]></content:encoded>
</item>
<item>
<title>Web interoperability: what the Interop project fixes</title>
<link>https://2haas.com/blog/web-interoperability-interop-project/</link>
<guid isPermaLink="true">https://2haas.com/blog/web-interoperability-interop-project/</guid>
<pubDate>Thu, 17 Sep 2026 07:18:06 GMT</pubDate>
<description>The Interop project gets Chrome, Firefox, Safari and Edge to agree on how web features behave. Here is how it works and what it changes for everyday frontend work.</description>
<category>Industry Updates</category>
<content:encoded><![CDATA[<p>"Works in Chrome, breaks in Safari" has cost frontend teams countless hours. A feature exists in every browser on paper, but each one handles the details a little differently, so developers write workarounds, add extra testing, or avoid the feature altogether.</p>
<p>The Interop project is the browser makers' joint answer to that.</p>
<h2 id="what-the-interop-project-is">What the Interop project is</h2>
<p>Interop is a yearly effort run by the companies behind the main browser engines, including Apple, Google, Microsoft and Mozilla, together with Igalia and Bocoup. Each year they:</p>
<ol>
<li>collect proposals for web features that behave inconsistently,</li>
<li>agree on a list of focus areas,</li>
<li>measure every browser against a shared set of automated tests from the web-platform-tests project,</li>
<li>publish the scores on a public dashboard.</li>
</ol>
<p>The public score matters. It turns compatibility from a vague goal into a number each team wants to raise.</p>
<h2 id="what-it-has-covered">What it has covered</h2>
<p>The focus areas are the features developers ask for most. Interop 2024 included:</p>
<ul>
<li>CSS nesting,</li>
<li>registered custom properties with <code>@property</code>,</li>
<li>the popover attribute,</li>
<li>relative color syntax,</li>
<li><code>text-wrap: balance</code>,</li>
<li>declarative shadow DOM,</li>
<li>scrollbar styling and URL parsing.</li>
</ul>
<p>Later rounds moved on to anchor positioning, view transitions and the Navigation API, among others. The pattern is the same each year: pick the painful gaps, close them together.</p>
<h2 id="what-changes-for-developers">What changes for developers</h2>
<p><strong>New features become usable sooner.</strong> A CSS feature used to take years to be safe everywhere. Features that go through Interop tend to reach every engine within a release cycle or two.</p>
<p><strong>Fewer workarounds.</strong> Less browser-specific code, fewer polyfills, shorter test matrices.</p>
<p><strong>A clearer signal.</strong> The Baseline label on MDN and Can I Use states whether a feature is newly available or widely available across browsers, so you do not have to read four compatibility tables to decide.</p>
<h2 id="what-still-needs-care">What still needs care</h2>
<p>Interop improves current browser versions. Your users are not all on current versions. Keep two habits:</p>
<ul>
<li>Check your own analytics before dropping support for older browsers.</li>
<li>Use progressive enhancement, so a missing feature degrades instead of breaking the page.</li>
</ul>
<pre tabindex="0"><code class="hljs language-css"><span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-keyword">@supports</span> (<span class="hljs-attribute">container-type</span>: inline-size) {
  <span class="hljs-selector-class">.card-wrap</span> {
    <span class="hljs-attribute">container-type</span>: inline-size;
  }
}
</code></pre>
<h2 id="the-takeaway">The takeaway</h2>
<p>The web platform is more consistent than it has ever been, and it keeps improving on a public schedule. Before reaching for a library to smooth over a browser difference, check whether the platform already does the job. Often it now does. <a href="https://2haas.com/blog/css-container-queries/">CSS container queries</a> are a good example.</p>]]></content:encoded>
</item>
<item>
<title>React Compiler: memoization without useMemo</title>
<link>https://2haas.com/blog/react-compiler-automatic-memoization/</link>
<guid isPermaLink="true">https://2haas.com/blog/react-compiler-automatic-memoization/</guid>
<pubDate>Wed, 16 Sep 2026 07:18:05 GMT</pubDate>
<description>React Compiler memoizes components and values at build time, so most useMemo, useCallback and React.memo calls become unnecessary. What it does and how to adopt it.</description>
<category>Tech News</category>
<content:encoded><![CDATA[<p>React re-renders a component whenever its state or its parent changes. To avoid repeating expensive work, developers have wrapped values in <code>useMemo</code>, functions in <code>useCallback</code> and components in <code>React.memo</code>. It works, but it is manual, easy to get wrong and noisy to read. A missing dependency causes a bug. A forgotten wrapper causes a slow screen.</p>
<p>React Compiler does that job for you at build time.</p>
<h2 id="what-it-changes">What it changes</h2>
<pre tabindex="0"><code class="hljs language-jsx"><span class="hljs-comment">// Before: memoization by hand</span>
<span class="hljs-keyword">const</span> visible = <span class="hljs-title function_">useMemo</span>(<span class="hljs-function">() =></span> items.<span class="hljs-title function_">filter</span>(matchesFilter), [items, matchesFilter]);
<span class="hljs-keyword">const</span> onSelect = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">(<span class="hljs-params">id</span>) =></span> <span class="hljs-title function_">select</span>(id), [select]);

<span class="hljs-comment">// With the compiler: plain code, the same result</span>
<span class="hljs-keyword">const</span> visible = items.<span class="hljs-title function_">filter</span>(matchesFilter);
<span class="hljs-keyword">const</span> <span class="hljs-title function_">onSelect</span> = (<span class="hljs-params">id</span>) => <span class="hljs-title function_">select</span>(id);
</code></pre>
<p>The compiler reads each component and hook, works out which values depend on which inputs, and inserts caching so that work is skipped when those inputs have not changed. It can memoize more precisely than most people do by hand, including values inside conditions.</p>
<h2 id="it-is-a-build-tool-not-a-react-19-feature">It is a build tool, not a React 19 feature</h2>
<p>The two are often mixed up because they were announced close together. React Compiler is a separate build step. Its first stable release, version 1.0, came in October 2025. It works with React 19 and also with React 17 and 18.</p>
<p>For what React 19 itself added, see <a href="https://2haas.com/blog/react-19-new-features/">React 19: Actions, the use API and Server Components</a>.</p>
<h2 id="how-to-adopt-it">How to adopt it</h2>
<ol>
<li><strong>Turn on the lint rules first.</strong> The compiler relies on the Rules of React: components must be pure, and props and state must not be mutated. The React hooks ESLint plugin reports code that breaks them.</li>
<li><strong>Install the plugin.</strong> The compiler ships as <code>babel-plugin-react-compiler</code>. Next.js and Vite each document a short setup for it.</li>
<li><strong>Start with part of the app.</strong> You can enable it for one folder, check the result, then widen it.</li>
<li><strong>Leave existing memoization alone at first.</strong> Old <code>useMemo</code> and <code>useCallback</code> calls keep working. Remove them gradually as you touch the files.</li>
</ol>
<p>If a specific component misbehaves, the <code>"use no memo"</code> directive opts it out while you investigate.</p>
<h2 id="what-it-does-not-fix">What it does not fix</h2>
<ul>
<li><strong>Slow work that genuinely has to run.</strong> Memoization skips repeated work. It does not make the first run faster.</li>
<li><strong>Huge lists.</strong> Thousands of rows still need pagination or virtualization.</li>
<li><strong>Data fetching problems.</strong> Request waterfalls and oversized payloads are outside its reach.</li>
<li><strong>Code that breaks the rules.</strong> The compiler skips components it cannot safely optimize, so those stay as slow as they were.</li>
</ul>
<h2 id="should-you-use-it">Should you use it?</h2>
<p>For a new React project, yes, from the first commit. For an existing codebase, enable the lint rules, fix what they report and roll the compiler out in stages. The result is less code to write, fewer re-render bugs and components that read like plain JavaScript again.</p>]]></content:encoded>
</item>
<item>
<title>Prompt engineering for developers: 5 habits that work</title>
<link>https://2haas.com/blog/prompt-engineering-for-developers/</link>
<guid isPermaLink="true">https://2haas.com/blog/prompt-engineering-for-developers/</guid>
<pubDate>Mon, 14 Sep 2026 07:18:04 GMT</pubDate>
<description>Vague prompts get vague code. Five habits make AI models far more useful for development work: clear instructions, context, examples, small steps and iteration.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>A language model answers the question you asked, not the one you meant. When the output is vague, wrong or oddly generic, the cause is usually a prompt that left too much to guess. Five habits fix most of it.</p>
<h2 id="1-say-exactly-what-you-want">1. Say exactly what you want</h2>
<p>Compare two requests:</p>
<pre tabindex="0"><code class="hljs language-text">Generate Python code for sorting.
</code></pre>
<pre tabindex="0"><code class="hljs language-text">Write a Python function that sorts a list of integers with quicksort.
Include type hints and a docstring, and show one example call.
</code></pre>
<p>The second names the language, the algorithm, the input and the quality bar. The model no longer has to choose for you.</p>
<h2 id="2-give-it-the-context-you-have">2. Give it the context you have</h2>
<p>The model cannot see your project. Tell it what matters:</p>
<ul>
<li>the stack and versions, for example Node.js 22 with TypeScript in strict mode,</li>
<li>the constraints, such as no new dependencies,</li>
<li>the audience or role: "review this as a senior backend developer",</li>
<li>the code or error message itself, pasted in full.</li>
</ul>
<p>A role is not magic. It works because it tells the model which details to care about.</p>
<h2 id="3-show-an-example-of-the-output">3. Show an example of the output</h2>
<p>One or two examples do more than a paragraph of description. If you want commit messages in a certain style, paste two good ones. If you want JSON, show the exact shape:</p>
<pre tabindex="0"><code class="hljs language-text">Return JSON only, in this shape:
{ "title": string, "tags": string[], "summary": string }
</code></pre>
<h2 id="4-break-big-tasks-into-steps">4. Break big tasks into steps</h2>
<p>"Build a login system" produces a wall of code that is hard to check. Split it: design the data model, then write the registration handler, then the tests. Review each piece before moving on. Small steps are easier for the model to get right and easier for you to verify.</p>
<h2 id="5-iterate-and-ask-it-to-check-itself">5. Iterate, and ask it to check itself</h2>
<p>Treat the first answer as a draft. Follow up with what is wrong: "the function fails on an empty list", "keep the public interface unchanged". It also helps to ask for a self review: "list the edge cases this does not handle". The answer often points to the real gaps.</p>
<h2 id="a-prompt-you-can-reuse">A prompt you can reuse</h2>
<pre tabindex="0"><code class="hljs language-text">Role: You are a senior TypeScript developer.
Context: Node.js 22, Express, PostgreSQL. No new dependencies.
Task: Write a function that validates the request body for POST /orders.
Rules: Return a list of field errors. Do not throw.
Output: Code first, then three test cases.
</code></pre>
<h2 id="know-the-limits">Know the limits</h2>
<p>A good prompt raises the quality of the answer. It does not make the answer true. Run the code, read the diff, and never paste secrets or customer data into a tool your company has not approved.</p>
<p>For a practical use of these habits, see our <a href="https://2haas.com/blog/refactoring-with-ai-assistants/">workflow for refactoring with AI assistants</a>.</p>]]></content:encoded>
</item>
<item>
<title>Git interactive rebase: clean commits before review</title>
<link>https://2haas.com/blog/git-interactive-rebase-clean-history/</link>
<guid isPermaLink="true">https://2haas.com/blog/git-interactive-rebase-clean-history/</guid>
<pubDate>Fri, 11 Sep 2026 23:03:05 GMT</pubDate>
<description>A branch full of wip and typo commits is hard to review. Interactive rebase lets you squash, reorder and reword commits into a history that explains the change.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>While you work, commits are save points: "wip", "fix typo", "try again". That is fine. Before a pull request, those save points are noise. A reviewer wants to read three clear steps, not eleven fragments.</p>
<p>Interactive rebase turns the first kind of history into the second.</p>
<h2 id="start-an-interactive-rebase">Start an interactive rebase</h2>
<p>To edit the last four commits:</p>
<pre tabindex="0"><code class="hljs language-bash">git rebase -i HEAD~4
</code></pre>
<p>Git opens an editor with one line per commit, oldest first.</p>
<pre tabindex="0"><code class="hljs language-text">pick a1b2c3d Add discount field to cart
pick d4e5f6a wip
pick 0718293 Fix typo
pick 4a5b6c7 Add discount tests
</code></pre>
<p>Change the word at the start of a line to tell Git what to do with that commit.</p>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Command</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pick</code></td>
<td>Keep the commit as it is</td>
</tr>
<tr>
<td><code>reword</code></td>
<td>Keep the changes, edit the message</td>
</tr>
<tr>
<td><code>squash</code></td>
<td>Merge into the commit above and combine the messages</td>
</tr>
<tr>
<td><code>fixup</code></td>
<td>Merge into the commit above and discard this message</td>
</tr>
<tr>
<td><code>edit</code></td>
<td>Stop here so you can change the commit</td>
</tr>
<tr>
<td><code>drop</code></td>
<td>Remove the commit</td>
</tr>
</tbody>
</table></div>
<p>You can also reorder lines to reorder commits.</p>
<pre tabindex="0"><code class="hljs language-text">pick a1b2c3d Add discount field to cart
fixup d4e5f6a wip
fixup 0718293 Fix typo
pick 4a5b6c7 Add discount tests
</code></pre>
<p>Save and close. Four commits become two that each mean something.</p>
<h2 id="mark-fixups-as-you-go">Mark fixups as you go</h2>
<p>When you fix something that belongs to an earlier commit, say so at commit time:</p>
<pre tabindex="0"><code class="hljs language-bash">git commit --fixup a1b2c3d
git rebase -i --autosquash main
</code></pre>
<p>Git places each fixup under its target automatically. You only confirm.</p>
<h2 id="update-the-remote-branch-safely">Update the remote branch safely</h2>
<p>A rebase rewrites commits, so a normal push is rejected. Use:</p>
<pre tabindex="0"><code class="hljs language-bash">git push --force-with-lease
</code></pre>
<p>Unlike <code>--force</code>, this refuses to overwrite the remote if someone else pushed in the meantime.</p>
<h2 id="rules-that-keep-you-out-of-trouble">Rules that keep you out of trouble</h2>
<ul>
<li><strong>Rewrite only your own branch.</strong> Never rebase <code>main</code> or a branch other people build on.</li>
<li><strong>Rebase before review, not during.</strong> Once reviewers have commented, add new commits so they can see what changed.</li>
<li><strong>You can always back out.</strong> <code>git rebase --abort</code> stops a rebase in progress, and <code>git reflog</code> lists every earlier state of the branch if you need to return to one.</li>
</ul>
<h2 id="stay-current-with-main">Stay current with main</h2>
<p>The same command keeps a feature branch up to date without merge commits:</p>
<pre tabindex="0"><code class="hljs language-bash">git fetch origin
git rebase origin/main
</code></pre>
<h2 id="why-it-is-worth-the-minute">Why it is worth the minute</h2>
<p>A clean history makes reviews faster, makes <code>git bisect</code> and <code>git revert</code> useful, and tells the next developer why each change was made. That includes you, six months from now.</p>]]></content:encoded>
</item>
<item>
<title>Speed up Docker builds with layer caching</title>
<link>https://2haas.com/blog/speed-up-docker-builds/</link>
<guid isPermaLink="true">https://2haas.com/blog/speed-up-docker-builds/</guid>
<pubDate>Thu, 10 Sep 2026 23:03:04 GMT</pubDate>
<description>Slow Docker builds delay every pull request. Order your Dockerfile for the layer cache, shrink the build context and use BuildKit cache mounts to cut build time.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>A Docker build that takes eight minutes is eight minutes added to every pull request and every deploy. Most of that time is usually spent reinstalling dependencies that did not change. Docker can skip that work if the Dockerfile lets it.</p>
<h2 id="how-the-layer-cache-works">How the layer cache works</h2>
<p>Docker builds an image one instruction at a time and caches the result of each one as a layer. On the next build it reuses a cached layer as long as the instruction and the files it depends on are unchanged. The moment one layer changes, every layer after it is rebuilt.</p>
<p>So the rule is simple: put what changes rarely at the top and what changes often at the bottom.</p>
<h2 id="copy-the-dependency-manifest-first">Copy the dependency manifest first</h2>
<p>This Dockerfile reinstalls every package whenever any source file changes:</p>
<pre tabindex="0"><code class="hljs language-dockerfile"><span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci</span>
</code></pre>
<p>This one reinstalls only when the dependencies change:</p>
<pre tabindex="0"><code class="hljs language-dockerfile"><span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>
</code></pre>
<p>It is the single most valuable change for build speed, and it works the same way for <code>requirements.txt</code>, <code>go.mod</code> or <code>composer.json</code>.</p>
<h2 id="keep-the-build-context-small">Keep the build context small</h2>
<p>Before the build starts, Docker sends the whole folder to the builder. A <code>.dockerignore</code> file keeps large and irrelevant paths out.</p>
<pre tabindex="0"><code class="hljs language-text">node_modules
.git
dist
coverage
</code></pre>
<p>A smaller context uploads faster and stops unrelated file changes from invalidating the cache.</p>
<h2 id="use-buildkit-cache-mounts">Use BuildKit cache mounts</h2>
<p>BuildKit, the default builder in current Docker versions, can keep a package manager's cache between builds, even when the layer itself has to be rebuilt.</p>
<pre tabindex="0"><code class="hljs language-dockerfile"><span class="hljs-keyword">RUN</span><span class="bash"> --mount=<span class="hljs-built_in">type</span>=cache,target=/root/.npm npm ci</span>
</code></pre>
<p>When a dependency changes, npm downloads only the new package instead of all of them.</p>
<h2 id="share-the-cache-in-ci">Share the cache in CI</h2>
<p>A fresh CI runner starts with an empty cache. Export it to your registry and import it on the next run.</p>
<pre tabindex="0"><code class="hljs language-bash">docker buildx build \
  --cache-from <span class="hljs-built_in">type</span>=registry,ref=registry.example.com/app:cache \
  --cache-to <span class="hljs-built_in">type</span>=registry,ref=registry.example.com/app:cache,mode=max \
  -t registry.example.com/app:latest --push .
</code></pre>
<h2 id="what-to-expect">What to expect</h2>
<p>With the manifest copied first and a shared cache, a build where only application code changed often drops from minutes to well under one. Faster builds mean faster feedback, and less paid CI time.</p>
<p>If the images themselves are heavy, the next step is to <a href="https://2haas.com/blog/reduce-docker-image-size/">reduce Docker image size with multi-stage builds</a>.</p>]]></content:encoded>
</item>
<item>
<title>WebP and AVIF: modern image formats with a fallback</title>
<link>https://2haas.com/blog/webp-avif-modern-image-formats/</link>
<guid isPermaLink="true">https://2haas.com/blog/webp-avif-modern-image-formats/</guid>
<pubDate>Wed, 09 Sep 2026 23:03:03 GMT</pubDate>
<description>WebP and AVIF produce much smaller files than JPEG and PNG at the same visual quality. How to convert your images and serve them with the picture element.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>JPEG dates from 1992 and PNG from 1996. Both still work everywhere, and both produce files far larger than necessary. Two newer formats compress much better and are supported by every current browser.</p>
<h2 id="the-formats-compared">The formats compared</h2>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Format</th>
<th>Best for</th>
<th>Typical size against JPEG</th>
<th>Transparency</th>
</tr>
</thead>
<tbody>
<tr>
<td>JPEG</td>
<td>Photos, as the universal fallback</td>
<td>Baseline</td>
<td>No</td>
</tr>
<tr>
<td>PNG</td>
<td>Screenshots and sharp graphics, as a fallback</td>
<td>Much larger for photos</td>
<td>Yes</td>
</tr>
<tr>
<td>WebP</td>
<td>Photos and graphics</td>
<td>About 25 to 35 percent smaller</td>
<td>Yes</td>
</tr>
<tr>
<td>AVIF</td>
<td>Photos</td>
<td>Often around half the size</td>
<td>Yes</td>
</tr>
</tbody>
</table></div>
<p>A 500 KB product photo in JPEG commonly becomes about 150 KB in WebP and less in AVIF, with no difference a visitor would notice. On a page with twenty such images, that is several megabytes saved.</p>
<h2 id="serve-them-with-a-fallback">Serve them with a fallback</h2>
<p>The <code>&#x3C;picture></code> element lets the browser pick the first format it understands.</p>
<pre tabindex="0"><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">picture</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">source</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/img/product.avif"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/avif"</span> /></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">source</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/img/product.webp"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/webp"</span> /></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">img</span>
    <span class="hljs-attr">src</span>=<span class="hljs-string">"/img/product.jpg"</span>
    <span class="hljs-attr">alt</span>=<span class="hljs-string">"Blue ceramic mug on a wooden table"</span>
    <span class="hljs-attr">width</span>=<span class="hljs-string">"800"</span>
    <span class="hljs-attr">height</span>=<span class="hljs-string">"600"</span>
    <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span>
  /></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">picture</span>></span>
</code></pre>
<p>A current browser takes the AVIF. One without AVIF support takes the WebP. Anything older falls back to the JPEG. The <code>alt</code>, <code>width</code> and <code>height</code> attributes go on the <code>&#x3C;img></code> and apply to whichever file is chosen.</p>
<h2 id="converting-your-images">Converting your images</h2>
<p><strong>One by one.</strong> Squoosh, a free tool that runs in the browser, shows the result side by side as you adjust the quality.</p>
<p><strong>On the command line.</strong></p>
<pre tabindex="0"><code class="hljs language-bash">cwebp -q 80 product.jpg -o product.webp
</code></pre>
<p><strong>In a build script.</strong> The <code>sharp</code> library for Node.js converts and resizes in one pass.</p>
<pre tabindex="0"><code class="hljs language-js"><span class="hljs-keyword">import</span> sharp <span class="hljs-keyword">from</span> <span class="hljs-string">"sharp"</span>;

<span class="hljs-keyword">const</span> source = <span class="hljs-title function_">sharp</span>(<span class="hljs-string">"product.jpg"</span>).<span class="hljs-title function_">resize</span>({ <span class="hljs-attr">width</span>: <span class="hljs-number">1600</span>, <span class="hljs-attr">withoutEnlargement</span>: <span class="hljs-literal">true</span> });

<span class="hljs-keyword">await</span> source.<span class="hljs-title function_">clone</span>().<span class="hljs-title function_">webp</span>({ <span class="hljs-attr">quality</span>: <span class="hljs-number">80</span> }).<span class="hljs-title function_">toFile</span>(<span class="hljs-string">"product.webp"</span>);
<span class="hljs-keyword">await</span> source.<span class="hljs-title function_">clone</span>().<span class="hljs-title function_">avif</span>({ <span class="hljs-attr">quality</span>: <span class="hljs-number">55</span> }).<span class="hljs-title function_">toFile</span>(<span class="hljs-string">"product.avif"</span>);
</code></pre>
<p>This site does the same: its images are converted to WebP during the build.</p>
<p><strong>Automatically.</strong> An image CDN or a framework image component can convert on request, based on what each browser says it accepts. You upload one original and never handle formats yourself.</p>
<h2 id="practical-advice">Practical advice</h2>
<ul>
<li><strong>Keep the originals.</strong> Convert from the source file each time, never from an already compressed copy.</li>
<li><strong>Judge quality with your eyes.</strong> The quality numbers mean different things in each format. An AVIF at 50 to 60 often matches a JPEG at 80.</li>
<li><strong>AVIF encodes slowly.</strong> For thousands of images, convert them in a build step or a queue, not during a request.</li>
<li><strong>Photos gain the most.</strong> Logos and icons should be SVG, which stays sharp at any size and is usually smaller still.</li>
<li><strong>Test graphics with fine text.</strong> AVIF can soften small details at low quality settings. Raise the quality or use WebP for those.</li>
</ul>
<h2 id="the-result">The result</h2>
<p>Changing formats is one of the largest performance wins for the least effort. Layout, markup and design stay the same, and the page gets lighter.</p>
<p>Format is one step of several. For resizing, responsive sizes and loading order, see <a href="https://2haas.com/blog/image-optimization-for-the-web/">image optimization for the web</a>. For when images load, see <a href="https://2haas.com/blog/native-image-lazy-loading/">native lazy loading</a>.</p>]]></content:encoded>
</item>
<item>
<title>Find performance bottlenecks with Chrome DevTools</title>
<link>https://2haas.com/blog/find-performance-bottlenecks-chrome-devtools/</link>
<guid isPermaLink="true">https://2haas.com/blog/find-performance-bottlenecks-chrome-devtools/</guid>
<pubDate>Sun, 06 Sep 2026 23:03:02 GMT</pubDate>
<description>Stop guessing why a page is slow. Use the Network and Performance panels in Chrome DevTools to find the heavy files, long tasks and layout shifts that cost you speed.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>When a page feels slow, the tempting move is to apply every tip you have read: compress something, defer something, hope. That wastes time, because most slow pages have one or two real causes. The browser already ships the tools to find them.</p>
<p>Open DevTools with <code>F12</code>, or right click the page and choose <strong>Inspect</strong>. Then work through the two panels below in order.</p>
<h2 id="start-with-the-network-panel">Start with the Network panel</h2>
<p>The Network panel answers one question: what did the page download, and how long did it take?</p>
<ol>
<li>Open the <strong>Network</strong> tab and tick <strong>Disable cache</strong>.</li>
<li>Set throttling to <strong>Fast 4G</strong> so you see what a phone user sees.</li>
<li>Reload the page.</li>
<li>Sort by <strong>Size</strong>, then by <strong>Time</strong>.</li>
</ol>
<p>Look for these patterns:</p>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>What you see</th>
<th>What it usually means</th>
</tr>
</thead>
<tbody>
<tr>
<td>An image of several hundred kilobytes</td>
<td>It needs compression, a modern format or smaller dimensions</td>
</tr>
<tr>
<td>One very large JavaScript file</td>
<td>The bundle needs splitting</td>
</tr>
<tr>
<td>Many small requests to other domains</td>
<td>Too many third party scripts</td>
</tr>
<tr>
<td>A long wait before the first byte of the HTML</td>
<td>A slow server or a missing cache</td>
</tr>
</tbody>
</table></div>
<p>The filter buttons (JS, CSS, Img, Font) make it quick to see which type of file dominates.</p>
<h2 id="record-in-the-performance-panel">Record in the Performance panel</h2>
<p>The Performance panel shows what the browser was busy doing.</p>
<ol>
<li>Open the <strong>Performance</strong> tab.</li>
<li>Turn on CPU throttling at <strong>4x slowdown</strong>.</li>
<li>Click the reload button inside the panel to record a page load.</li>
<li>Stop the recording once the page settles.</li>
</ol>
<p>In the timeline, look for:</p>
<ul>
<li><strong>Long tasks.</strong> Blocks marked with a red corner ran longer than 50 ms and kept the page from responding to input.</li>
<li><strong>Render blocking resources.</strong> Stylesheets and scripts in the <code>&#x3C;head></code> that delay the first paint.</li>
<li><strong>Layout shifts.</strong> Content that jumps while the page loads, often an image without <code>width</code> and <code>height</code>.</li>
<li><strong>The LCP marker.</strong> It shows which element was the largest paint and when it appeared.</li>
</ul>
<p>Click any block to see the function and file behind it. That is the line of code to fix.</p>
<h2 id="turn-the-findings-into-fixes">Turn the findings into fixes</h2>
<p>A typical recording shows a large hero image loading early and one long script blocking the main thread. Each has a direct answer:</p>
<ul>
<li>Heavy images: see our guide to <a href="https://2haas.com/blog/image-optimization-for-the-web/">image optimization for the web</a>.</li>
<li>A large bundle: <a href="https://2haas.com/blog/reduce-javascript-bundle-size/">reduce the JavaScript bundle size</a>.</li>
<li>Long tasks: <a href="https://2haas.com/blog/javascript-runtime-performance/">keep the main thread free</a> or move the work to a <a href="https://2haas.com/blog/web-workers-offload-heavy-tasks/">Web Worker</a>.</li>
</ul>
<h2 id="measure-again">Measure again</h2>
<p>Change one thing, record again and compare. Performance work without a second measurement is only a guess. For a quick overall score, run a Lighthouse report from the same DevTools window, and remember that it is a lab test: real users on real phones are the final judge.</p>]]></content:encoded>
</item>
<item>
<title>Structured logging: make your logs searchable</title>
<link>https://2haas.com/blog/structured-logging/</link>
<guid isPermaLink="true">https://2haas.com/blog/structured-logging/</guid>
<pubDate>Fri, 04 Sep 2026 23:03:04 GMT</pubDate>
<description>Plain text logs are easy to write and painful to search. Structured JSON logs with levels and consistent fields turn finding one failed order into a single query.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>A log line written for a human looks like this:</p>
<pre tabindex="0"><code class="hljs language-text">Order failed for user 4821 product 77: card declined
</code></pre>
<p>It reads well. It searches badly. To find every failed order for one user you need a regular expression, and it breaks the day someone rewords the message.</p>
<p>The same event as a structured log:</p>
<pre tabindex="0"><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"level"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"error"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"time"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2026-09-04T10:15:02Z"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"msg"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Order failed"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"userId"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4821</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"productId"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">77</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"reason"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"card_declined"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"requestId"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"b7f3c2"</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>Now every value is a field. "All errors for user 4821 in the last hour" is a filter, not a text hunt.</p>
<h2 id="use-a-logging-library">Use a logging library</h2>
<p>Do not build this with <code>console.log</code> and string concatenation. Every language has a library that does it properly.</p>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Language</th>
<th>Common choices</th>
</tr>
</thead>
<tbody>
<tr>
<td>Node.js</td>
<td>Pino, Winston</td>
</tr>
<tr>
<td>Python</td>
<td>The standard <code>logging</code> module with a JSON formatter, structlog</td>
</tr>
<tr>
<td>Java</td>
<td>Logback or Log4j 2 with a JSON encoder</td>
</tr>
<tr>
<td>Go</td>
<td>The standard <code>log/slog</code> package</td>
</tr>
</tbody>
</table></div>
<p>In Node.js with Pino:</p>
<pre tabindex="0"><code class="hljs language-js"><span class="hljs-keyword">import</span> pino <span class="hljs-keyword">from</span> <span class="hljs-string">"pino"</span>;

<span class="hljs-keyword">const</span> logger = <span class="hljs-title function_">pino</span>({ <span class="hljs-attr">level</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">LOG_LEVEL</span> ?? <span class="hljs-string">"info"</span> });

logger.<span class="hljs-title function_">error</span>({ userId, productId, <span class="hljs-attr">reason</span>: <span class="hljs-string">"card_declined"</span> }, <span class="hljs-string">"Order failed"</span>);
</code></pre>
<h2 id="use-levels-on-purpose">Use levels on purpose</h2>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Level</th>
<th>Use it for</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>debug</code></td>
<td>Detail that helps during development. Off in production.</td>
</tr>
<tr>
<td><code>info</code></td>
<td>Normal events worth recording: order placed, job finished.</td>
</tr>
<tr>
<td><code>warn</code></td>
<td>Something unexpected that the system handled.</td>
</tr>
<tr>
<td><code>error</code></td>
<td>A failure that needs attention.</td>
</tr>
</tbody>
</table></div>
<p>If everything is an error, nothing is.</p>
<h2 id="fields-worth-adding-to-every-line">Fields worth adding to every line</h2>
<ul>
<li>a timestamp in UTC,</li>
<li>the service name and environment,</li>
<li>a request or correlation ID, passed along to every service the request touches,</li>
<li>the user or account ID, where your privacy rules allow it.</li>
</ul>
<p>The request ID is the most valuable of these. It lets you pull every line that belongs to one request, across all services.</p>
<h2 id="what-never-goes-in-a-log">What never goes in a log</h2>
<p>Passwords, access tokens, card numbers and personal data do not belong in logs. Most libraries can redact named fields automatically. Configure that once instead of trusting every developer to remember.</p>
<h2 id="the-payoff">The payoff</h2>
<p>A customer reports a failed order. With structured logs you filter by their user ID and <code>level: error</code>, see the reason and the request ID, and follow that ID through the payment service. Minutes, not an afternoon.</p>
<p>Logs are one of three signals. See how they work with metrics and traces in <a href="https://2haas.com/blog/microservice-observability-logs-metrics-traces/">microservice observability</a>.</p>]]></content:encoded>
</item>
<item>
<title>How to reduce JavaScript bundle size</title>
<link>https://2haas.com/blog/reduce-javascript-bundle-size/</link>
<guid isPermaLink="true">https://2haas.com/blog/reduce-javascript-bundle-size/</guid>
<pubDate>Thu, 03 Sep 2026 18:25:10 GMT</pubDate>
<description>Every kilobyte of JavaScript is downloaded, parsed and run before a page responds. Measure the bundle, split it by route, lazy load the rest and trim dependencies.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>JavaScript is the most expensive thing a page ships. An image only has to be decoded. A script has to be downloaded, parsed, compiled and executed, all on the main thread, before the page reacts to a tap. On a mid-range phone, a large bundle is the difference between a page that responds and one that feels stuck.</p>
<p>Here is the order we work in.</p>
<h2 id="1-measure-before-you-cut">1. Measure before you cut</h2>
<p>You cannot shrink what you cannot see. A bundle analyzer draws the bundle as a map of boxes, one per module, sized by weight.</p>
<ul>
<li>Webpack: <code>webpack-bundle-analyzer</code></li>
<li>Vite and Rollup: <code>rollup-plugin-visualizer</code></li>
<li>Any build with source maps: <code>source-map-explorer</code></li>
</ul>
<p>The surprises are usually the same: a date library with every locale, a full icon set, or two versions of the same package.</p>
<h2 id="2-split-the-code-by-route">2. Split the code by route</h2>
<p>A visitor on the home page does not need the checkout code. Dynamic <code>import()</code> tells the bundler to put a module in its own file and load it on demand.</p>
<pre tabindex="0"><code class="hljs language-jsx"><span class="hljs-keyword">import</span> { lazy, <span class="hljs-title class_">Suspense</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title class_">Checkout</span> = <span class="hljs-title function_">lazy</span>(<span class="hljs-function">() =></span> <span class="hljs-keyword">import</span>(<span class="hljs-string">"./pages/Checkout"</span>));

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&#x3C;<span class="hljs-attr">p</span>></span>Loading…<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>}>
      <span class="hljs-tag">&#x3C;<span class="hljs-name">Checkout</span> /></span>
    <span class="hljs-tag">&#x3C;/<span class="hljs-name">Suspense</span>></span></span>
  );
}
</code></pre>
<p>Frameworks such as Next.js split by route for you. In a custom setup, routes are the first place to add it.</p>
<h2 id="3-lazy-load-heavy-features">3. Lazy load heavy features</h2>
<p>Some components are large and rarely needed at first paint: charts, rich text editors, maps, image galleries. Load them when the user opens the tab or scrolls near them, not on page load.</p>
<p>On a shop, that means the gallery script loads on the product page and the payment scripts load on the checkout page. Nothing else pays for them.</p>
<h2 id="4-trim-the-dependencies">4. Trim the dependencies</h2>
<ul>
<li>Replace heavy libraries with lighter ones, or with the platform. <code>Intl.DateTimeFormat</code> covers many uses of a date library.</li>
<li>Import only what you use, and make sure it can be removed. See <a href="https://2haas.com/blog/tree-shaking-javascript/">how tree shaking really works</a>.</li>
<li>Check the cost of a package before you add it.</li>
</ul>
<h2 id="5-ship-a-real-production-build">5. Ship a real production build</h2>
<ul>
<li>Minify the code and serve it with Brotli or gzip compression.</li>
<li>Build in production mode so development warnings are stripped.</li>
<li>Target modern browsers. Transpiling for very old ones adds helper code and polyfills that most visitors never need.</li>
</ul>
<h2 id="6-keep-it-small">6. Keep it small</h2>
<p>Bundles grow back one import at a time. Add a size budget to CI with a tool such as <code>size-limit</code>, so a pull request that adds 80 kB fails with a clear message instead of slipping through.</p>
<h2 id="what-to-expect">What to expect</h2>
<p>Route splitting and one or two dependency swaps commonly cut the initial JavaScript by a third or more. To confirm the result on a real page, <a href="https://2haas.com/blog/find-performance-bottlenecks-chrome-devtools/">measure it with Chrome DevTools</a>.</p>]]></content:encoded>
</item>
<item>
<title>Dependency hygiene: audit, update and prune npm packages</title>
<link>https://2haas.com/blog/dependency-hygiene-npm/</link>
<guid isPermaLink="true">https://2haas.com/blog/dependency-hygiene-npm/</guid>
<pubDate>Wed, 02 Sep 2026 23:11:29 GMT</pubDate>
<description>Outdated and unused packages are a security risk and a tax on every install. A short routine with npm audit, npm outdated and a cleanup tool keeps a project healthy.</description>
<category>Tech Tips</category>
<content:encoded><![CDATA[<p>A typical Node.js project depends on a handful of packages you chose and hundreds you did not. Each one can carry a vulnerability, break on an update, or sit unused while still being installed on every build. Left alone, dependencies rot. Updating a project that is three years behind is a project in itself.</p>
<p>A small routine prevents that.</p>
<h2 id="1-audit-for-known-vulnerabilities">1. Audit for known vulnerabilities</h2>
<pre tabindex="0"><code class="hljs language-bash">npm audit
</code></pre>
<p>The report lists each known vulnerability, its severity and the package that brought it in. <code>npm audit fix</code> applies the updates that stay within your version ranges.</p>
<p>Be careful with <code>npm audit fix --force</code>. It can install new major versions with breaking changes. Read what it plans to do first.</p>
<h2 id="2-see-what-is-out-of-date">2. See what is out of date</h2>
<pre tabindex="0"><code class="hljs language-bash">npm outdated
</code></pre>
<p>The output has three columns that matter: the version you have, the newest version your range allows, and the latest release.</p>
<ul>
<li><code>npm update</code> moves everything to the newest allowed version. This is usually safe.</li>
<li>Major versions need a decision. Take them one at a time, read the changelog and run the tests.</li>
</ul>
<p>Small, regular updates are easy. Rare, giant ones are where projects get stuck.</p>
<h2 id="3-remove-what-you-no-longer-use">3. Remove what you no longer use</h2>
<p>Packages outlive the code that needed them. A tool can find them:</p>
<pre tabindex="0"><code class="hljs language-bash">npx depcheck
<span class="hljs-comment"># or</span>
npx knip
</code></pre>
<p>Review the list, then remove the leftovers with <code>npm uninstall &#x3C;package></code>. <code>npm prune</code> cleans <code>node_modules</code> of anything that is not in <code>package.json</code>.</p>
<p>Fewer dependencies mean faster installs, a smaller attack surface and fewer update notifications.</p>
<h2 id="4-make-installs-reproducible">4. Make installs reproducible</h2>
<ul>
<li>Commit <code>package-lock.json</code>.</li>
<li>Use <code>npm ci</code> in CI and in Docker builds. It installs exactly what the lockfile says and fails if the two disagree.</li>
</ul>
<h2 id="5-let-automation-do-the-reminding">5. Let automation do the reminding</h2>
<ul>
<li>Dependabot or Renovate opens a pull request when an update is available, with the changelog attached.</li>
<li>Add <code>npm audit --audit-level=high</code> to CI so a serious vulnerability fails the build instead of waiting to be noticed.</li>
</ul>
<h2 id="a-monthly-checklist">A monthly checklist</h2>
<ol>
<li>Run <code>npm audit</code> and fix what it reports.</li>
<li>Run <code>npm outdated</code> and apply minor and patch updates.</li>
<li>Plan one major upgrade if any are pending.</li>
<li>Run a cleanup tool and remove unused packages.</li>
<li>Run the tests and deploy.</li>
</ol>
<p>Half an hour a month keeps the project current and the next big upgrade small.</p>
<p>For the wider picture, see <a href="https://2haas.com/blog/software-supply-chain-security-sbom/">software supply chain security</a>. If you manage several packages in one repository, see <a href="https://2haas.com/blog/pnpm-workspaces-monorepo-dependencies/">monorepo dependencies with pnpm and npm workspaces</a>.</p>]]></content:encoded>
</item>
<item>
<title>Microservice observability: logs, metrics and traces</title>
<link>https://2haas.com/blog/microservice-observability-logs-metrics-traces/</link>
<guid isPermaLink="true">https://2haas.com/blog/microservice-observability-logs-metrics-traces/</guid>
<pubDate>Tue, 01 Sep 2026 23:11:09 GMT</pubDate>
<description>When a request crosses ten services, one log file cannot explain a slow checkout. Logs, metrics and traces each answer a different question. Here is how they fit.</description>
<category>Guides</category>
<content:encoded><![CDATA[<p>In a single application, a slow request is a matter of reading one log. In a system of microservices, the same request passes through a gateway, three services, a queue and two databases. Each one says it was fine. The user still waited eight seconds.</p>
<p>Observability is how you answer "what happened?" without guessing. It rests on three signals.</p>
<h2 id="three-signals-three-questions">Three signals, three questions</h2>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Signal</th>
<th>Question it answers</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>Metrics</td>
<td>Is something wrong?</td>
<td>Checkout latency doubled at 14:05</td>
</tr>
<tr>
<td>Traces</td>
<td>Where is it wrong?</td>
<td>2.4 seconds were spent in the inventory service</td>
</tr>
<tr>
<td>Logs</td>
<td>Why is it wrong?</td>
<td>Database connection timed out, pool exhausted</td>
</tr>
</tbody>
</table></div>
<p>Each is weak alone. Together they take you from an alert to a cause.</p>
<h2 id="metrics-the-early-warning">Metrics: the early warning</h2>
<p>Metrics are numbers measured over time. They are cheap to store and ideal for dashboards and alerts. For every service, start with three:</p>
<ul>
<li><strong>Rate:</strong> requests per second,</li>
<li><strong>Errors:</strong> the share of requests that fail,</li>
<li><strong>Duration:</strong> how long requests take, as percentiles and not as an average.</li>
</ul>
<p>An average hides the slow requests your unhappiest users experience. The 95th and 99th percentiles show them.</p>
<h2 id="traces-the-journey-of-one-request">Traces: the journey of one request</h2>
<p>A trace follows a single request across services. Each service adds a span with its start time and duration, and passes a trace ID to the next service in a request header. The result is a timeline: which service called which, and where the time went.</p>
<p>Traces are what make a distributed system readable.</p>
<h2 id="logs-the-detail">Logs: the detail</h2>
<p>Logs record individual events. They become far more useful when they are <a href="https://2haas.com/blog/structured-logging/">structured</a> and when every line carries the trace ID. Then "show me the logs for this slow request" is one filter.</p>
<h2 id="use-one-standard-to-collect-them">Use one standard to collect them</h2>
<p>OpenTelemetry is the vendor-neutral standard for producing all three signals. Its libraries instrument common frameworks, HTTP clients and database drivers automatically, and the data can be sent to the backend of your choice. Instrumenting once and keeping the freedom to change tools later is worth a lot.</p>
<h2 id="a-worked-example">A worked example</h2>
<ol>
<li>An alert fires: the 95th percentile of checkout latency is above two seconds.</li>
<li>You open a slow trace. Most of the time sits in a single span, the call to the inventory service.</li>
<li>The metrics for that service show CPU at its limit on one instance.</li>
<li>The logs for the same trace ID show repeated database connection timeouts.</li>
</ol>
<p>The cause is an exhausted connection pool. Finding it took four steps and no guesswork.</p>
<h2 id="where-to-start">Where to start</h2>
<ol>
<li>Structured logs with a request ID in every service.</li>
<li>Rate, error and duration metrics, with one dashboard per service.</li>
<li>Tracing on the most important path, such as checkout or login.</li>
<li>Alerts on what users feel, such as latency and failed requests, not on every CPU spike.</li>
</ol>
<p>Observability tells you about failures. Handling them well is the other half: see <a href="https://2haas.com/blog/error-handling-microservices-circuit-breakers-retries/">error handling in microservices</a>.</p>]]></content:encoded>
</item>
<item>
<title>React 19: Actions, the use API and Server Components</title>
<link>https://2haas.com/blog/react-19-new-features/</link>
<guid isPermaLink="true">https://2haas.com/blog/react-19-new-features/</guid>
<pubDate>Sun, 30 Aug 2026 08:59:04 GMT</pubDate>
<description>React 19 made forms and async state much simpler with Actions, useActionState and useOptimistic, added the use API and stabilized Server Components. A practical summary.</description>
<category>Tech News</category>
<content:encoded><![CDATA[<p>React 19 became stable in December 2024. Its theme is removing code you used to write by hand: loading flags, error state, optimistic updates and wrappers such as <code>forwardRef</code>. This summary covers the changes that affect everyday work.</p>
<p>One correction to a common mix-up first: concurrent rendering arrived in React 18. React 19 builds on it.</p>
<h2 id="actions-async-work-without-the-boilerplate">Actions: async work without the boilerplate</h2>
<p>Submitting a form used to need three pieces of state: the data, a pending flag and an error. React 19 lets you pass an async function straight to a form, and tracks the rest.</p>
<pre tabindex="0"><code class="hljs language-jsx"><span class="hljs-keyword">import</span> { useActionState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">NewsletterForm</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> [error, submit, isPending] = <span class="hljs-title function_">useActionState</span>(<span class="hljs-title function_">async</span> (previous, formData) => {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title function_">subscribe</span>(formData.<span class="hljs-title function_">get</span>(<span class="hljs-string">"email"</span>));
    <span class="hljs-keyword">return</span> result.<span class="hljs-property">ok</span> ? <span class="hljs-literal">null</span> : result.<span class="hljs-property">message</span>;
  }, <span class="hljs-literal">null</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">{submit}</span>></span>
      <span class="hljs-tag">&#x3C;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">required</span> /></span>
      <span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">disabled</span>=<span class="hljs-string">{isPending}</span>></span>{isPending ? "Saving…" : "Subscribe"}<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
      {error &#x26;&#x26; <span class="hljs-tag">&#x3C;<span class="hljs-name">p</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>></span>{error}<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span>}
    <span class="hljs-tag">&#x3C;/<span class="hljs-name">form</span>></span></span>
  );
}
</code></pre>
<p>Three hooks work together here:</p>
<div class="table-scroll" tabindex="0" role="region" aria-label="Table"><table>
<thead>
<tr>
<th>Hook</th>
<th>What it gives you</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>useActionState</code></td>
<td>The result of the last submission and a pending flag</td>
</tr>
<tr>
<td><code>useFormStatus</code></td>
<td>The pending state of the parent form, for a shared submit button</td>
</tr>
<tr>
<td><code>useOptimistic</code></td>
<td>A temporary value shown while the request is in flight</td>
</tr>
</tbody>
</table></div>
<h2 id="the-use-api">The use API</h2>
<p><code>use</code> reads a promise or a context during render. Unlike hooks, it can be called inside a condition or a loop.</p>
<pre tabindex="0"><code class="hljs language-jsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Comments</span>(<span class="hljs-params">{ commentsPromise }</span>) {
  <span class="hljs-keyword">const</span> comments = <span class="hljs-title function_">use</span>(commentsPromise); <span class="hljs-comment">// suspends until the data arrives</span>
  <span class="hljs-keyword">return</span> comments.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">c</span>) =></span> <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">p</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{c.id}</span>></span>{c.text}<span class="hljs-tag">&#x3C;/<span class="hljs-name">p</span>></span></span>);
}
</code></pre>
<p>Wrapped in <code>&#x3C;Suspense></code>, this replaces a good deal of <code>useEffect</code> data fetching.</p>
<h2 id="server-components-and-server-functions">Server Components and Server Functions</h2>
<p>Server Components are stable in React 19. They render on the server and send no JavaScript of their own to the browser, which is what keeps large pages light. A component can read from a database directly, and only the interactive parts become client code.</p>
<p>Server Functions let a client component call a function that runs on the server, without you writing an API route for it. Both features need a framework that supports them, such as Next.js.</p>
<h2 id="smaller-changes-that-add-up">Smaller changes that add up</h2>
<ul>
<li><strong><code>ref</code> is a normal prop.</strong> Function components no longer need <code>forwardRef</code>.</li>
<li><strong>Document metadata.</strong> <code>&#x3C;title></code>, <code>&#x3C;meta></code> and <code>&#x3C;link></code> tags rendered inside a component are moved to the document head.</li>
<li><strong>Better hydration errors.</strong> A mismatch now shows a diff instead of a vague warning.</li>
<li><strong>Custom elements.</strong> Web components work with props as expected.</li>
</ul>
<h2 id="should-you-upgrade">Should you upgrade?</h2>
<p>For most apps, yes. The React team published an upgrade guide and codemods that handle the mechanical changes. Move to the latest 18 release first, clear the warnings, then go to 19.</p>
<p>Automatic memoization is a separate tool. See <a href="https://2haas.com/blog/react-compiler-automatic-memoization/">React Compiler: memoization without useMemo</a>.</p>]]></content:encoded>
</item>
<item>
<title>Software supply chain security: SBOMs and signed builds</title>
<link>https://2haas.com/blog/software-supply-chain-security-sbom/</link>
<guid isPermaLink="true">https://2haas.com/blog/software-supply-chain-security-sbom/</guid>
<pubDate>Sat, 29 Aug 2026 08:59:10 GMT</pubDate>
<description>Most of your application is code you did not write. SBOMs, automated scanning, trusted registries and signed artifacts show what you ship and prove where it came from.</description>
<category>Industry Updates</category>
<content:encoded><![CDATA[<p>A modern application is mostly other people's code. Your team writes a few thousand lines and installs hundreds of packages, a base image and a set of CI actions around them. Attackers noticed. Instead of attacking one company, they compromise a library or a build tool and reach everyone who uses it.</p>
<p>Log4Shell, the SolarWinds breach and the backdoor found in the xz compression library all followed that pattern. The industry response is now fairly settled, and regulation is moving the same way: the United States asks federal software suppliers for a bill of materials, and the EU's Cyber Resilience Act brings similar duties for products sold in Europe.</p>
<p>Four practices make up that response.</p>
<h2 id="1-know-what-you-ship">1. Know what you ship</h2>
<p>A Software Bill of Materials (SBOM) is the ingredient list of a build: every component, its version and its licence. When the next serious vulnerability is announced, the question "are we affected?" becomes a search, not a week of investigation.</p>
<p>Generate one on every build. The two common formats are CycloneDX and SPDX.</p>
<pre tabindex="0"><code class="hljs language-bash">npm sbom --sbom-format cyclonedx > sbom.json
</code></pre>
<p>Tools such as Syft and Trivy do the same for container images and other ecosystems.</p>
<h2 id="2-scan-automatically">2. Scan automatically</h2>
<p>A scanner compares your components with databases of known vulnerabilities. Run it in three places:</p>
<ul>
<li>on every pull request, so a risky package is caught before it merges,</li>
<li>on every build, as a gate before deployment,</li>
<li>on a schedule, because new vulnerabilities are found in code you shipped months ago.</li>
</ul>
<p>Fail the pipeline on critical findings. A report nobody reads protects nothing.</p>
<h2 id="3-pull-from-sources-you-trust">3. Pull from sources you trust</h2>
<ul>
<li>Commit lockfiles, so every build installs the same versions.</li>
<li>Use an internal registry or proxy for packages and base images.</li>
<li>Pin CI actions and base images to an exact version or digest, not to a moving tag.</li>
<li>Check a new package before adding it: who maintains it, how widely it is used, and whether the name is a near copy of a popular one.</li>
</ul>
<h2 id="4-sign-what-you-build">4. Sign what you build</h2>
<p>Signing answers a different question: is this artifact really the one our pipeline produced? Tools from the Sigstore project, such as cosign, sign container images and verify them at deploy time. Build provenance goes one step further and records which source commit and which workflow produced the artifact.</p>
<p>If the deploy step refuses anything unsigned, a tampered image cannot reach production quietly.</p>
<h2 id="how-it-fits-together">How it fits together</h2>
<p>A typical pipeline:</p>
<ol>
<li>Build the application and the image.</li>
<li>Generate the SBOM.</li>
<li>Scan the SBOM and the image. Stop on critical vulnerabilities.</li>
<li>Sign the image and attach the SBOM.</li>
<li>Deploy, verifying the signature first.</li>
</ol>
<h2 id="where-to-start">Where to start</h2>
<p>You do not need all of it at once. Turn on dependency alerts in your repository host, add a scanner to CI, and commit your lockfiles. That covers the most common failures. SBOMs and signing follow naturally once the pipeline is in place.</p>
<p>The day-to-day side of this is plain upkeep. See <a href="https://2haas.com/blog/dependency-hygiene-npm/">dependency hygiene for npm projects</a>.</p>]]></content:encoded>
</item>
</channel>
</rss>