<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: CertosinoLab</title>
    <description>The latest articles on DEV Community by CertosinoLab (@certosinolab).</description>
    <link>https://dev.to/certosinolab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1078424%2F1c21f990-fd64-41bf-998b-356eca974d80.png</url>
      <title>DEV Community: CertosinoLab</title>
      <link>https://dev.to/certosinolab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/certosinolab"/>
    <language>en</language>
    <item>
      <title>Building AI Prompt Lab with Java 21, Spring Boot and React 19</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Sun, 16 Aug 2026 23:20:00 +0000</pubDate>
      <link>https://dev.to/certosinolab/building-ai-prompt-lab-with-java-21-spring-boot-and-react-19-46p8</link>
      <guid>https://dev.to/certosinolab/building-ai-prompt-lab-with-java-21-spring-boot-and-react-19-46p8</guid>
      <description>&lt;p&gt;AI Prompt Lab is a full-stack application I built around a simple idea: managing reusable AI prompts should feel like working with any other structured application asset, rather than keeping them scattered across notes, text files or chat histories.&lt;/p&gt;

&lt;p&gt;The project combines &lt;strong&gt;Java 21, Spring Boot, React 19, TypeScript, PostgreSQL and OpenRouter&lt;/strong&gt; in a compact architecture that covers the main concerns of a modern web application: authentication, authorization, persistence, external API integration and management of sensitive configuration.&lt;/p&gt;

&lt;p&gt;I deliberately kept the system relatively simple. The goal was not to introduce architectural patterns for their own sake, but to build a clean application where each technology has a clear responsibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;AI Prompt Lab provides an authenticated workspace where users can create, update, organize and reuse prompts for generative AI models.&lt;/p&gt;

&lt;p&gt;Each user has an independent prompt collection and can configure an OpenRouter account to interact with different language models through the application.&lt;/p&gt;

&lt;p&gt;At a high level, the application provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user authentication;&lt;/li&gt;
&lt;li&gt;role-based authorization;&lt;/li&gt;
&lt;li&gt;personal prompt management;&lt;/li&gt;
&lt;li&gt;OpenRouter configuration;&lt;/li&gt;
&lt;li&gt;AI chat functionality;&lt;/li&gt;
&lt;li&gt;encrypted storage of external API credentials;&lt;/li&gt;
&lt;li&gt;administrative user management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From an architectural point of view, this makes the project more interesting than a conventional CRUD application while still remaining small enough to keep the overall design easy to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technology stack
&lt;/h2&gt;

&lt;p&gt;The repository is divided into two independent applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend/
frontend/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Java 21&lt;/li&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;li&gt;Spring Web&lt;/li&gt;
&lt;li&gt;Spring Data JPA&lt;/li&gt;
&lt;li&gt;Spring Security&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Flyway&lt;/li&gt;
&lt;li&gt;OpenRouter API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;React Router&lt;/li&gt;
&lt;li&gt;React Context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stack is intentionally conventional.&lt;/p&gt;

&lt;p&gt;There is no microservice decomposition, no external state management library on the frontend and no additional infrastructure that the application does not currently require.&lt;/p&gt;

&lt;p&gt;For this scope, keeping the system as a modular monolith provides a much better balance between maintainability, deployment complexity and development speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backend architecture
&lt;/h2&gt;

&lt;p&gt;The Spring Boot application follows a traditional layered structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controller
service
repository
model
config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The separation is straightforward.&lt;/p&gt;

&lt;p&gt;Controllers define the HTTP API, services contain application logic, repositories encapsulate persistence, and configuration components handle cross-cutting concerns such as security and encryption.&lt;/p&gt;

&lt;p&gt;I prefer this approach for an application of this size because the control flow remains explicit. A request enters through a controller, moves through the service layer and reaches the persistence layer without introducing unnecessary indirection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt management
&lt;/h3&gt;

&lt;p&gt;Prompt management is the central domain of the application.&lt;/p&gt;

&lt;p&gt;Authenticated users can create, edit, delete and retrieve prompts associated with their account.&lt;/p&gt;

&lt;p&gt;The backend also supports sorting directly at the repository level, keeping data-oriented operations close to the persistence layer instead of reimplementing them in the client.&lt;/p&gt;

&lt;p&gt;The result is a small REST API with predictable resource-oriented operations and a clean separation between client-side presentation and server-side data access.&lt;/p&gt;




&lt;h2&gt;
  
  
  Authentication and authorization
&lt;/h2&gt;

&lt;p&gt;Authentication is handled entirely by Spring Security.&lt;/p&gt;

&lt;p&gt;Passwords are persisted using BCrypt hashing, while authenticated sessions are represented by a server-issued token stored in an HTTP-only cookie.&lt;/p&gt;

&lt;p&gt;The browser sends the cookie automatically with subsequent requests, and a custom Spring Security filter restores the authenticated user before the request reaches the application layer.&lt;/p&gt;

&lt;p&gt;The flow is essentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Login request
      |
      v
Credential verification
      |
      v
Session token creation
      |
      v
HTTP-only cookie
      |
      v
Spring Security filter
      |
      v
Authenticated request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authorization is enforced on the backend rather than being delegated to the user interface.&lt;/p&gt;

&lt;p&gt;The application distinguishes between regular users and administrators, and administrative endpoints are protected through Spring Security.&lt;/p&gt;

&lt;p&gt;The frontend can therefore use roles to adapt navigation and presentation, while the server remains the authoritative boundary for access control.&lt;/p&gt;




&lt;h2&gt;
  
  
  PostgreSQL and schema management
&lt;/h2&gt;

&lt;p&gt;PostgreSQL provides the persistence layer, with Spring Data JPA handling repository access and entity mapping.&lt;/p&gt;

&lt;p&gt;Schema evolution is managed through Flyway migrations.&lt;/p&gt;

&lt;p&gt;I prefer keeping database changes explicit and versioned alongside the application rather than depending on automatic schema mutation at runtime.&lt;/p&gt;

&lt;p&gt;This gives the database the same kind of traceability as the rest of the codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;V1__init_database.sql
V2__future_change.sql
V3__another_change.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also makes the application easier to move between environments because schema creation and evolution are part of a repeatable process rather than a manual deployment step.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenRouter integration
&lt;/h2&gt;

&lt;p&gt;AI requests are handled by the backend rather than being sent directly from React to the external provider.&lt;/p&gt;

&lt;p&gt;The frontend sends the conversation to the Spring Boot API, the backend loads the current user's AI configuration and then performs the request to OpenRouter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React
  |
  v
Spring Boot API
  |
  v
User AI configuration
  |
  v
OpenRouter
  |
  v
Language model
  |
  v
Response returned to React
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This boundary keeps provider-specific behavior outside the frontend and gives the backend control over authentication headers, request construction and external API communication.&lt;/p&gt;

&lt;p&gt;It also leaves room for future extensions.&lt;/p&gt;

&lt;p&gt;OpenRouter could eventually become one implementation behind a generic AI provider interface without requiring significant changes to the UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling external API credentials
&lt;/h2&gt;

&lt;p&gt;Allowing users to configure OpenRouter introduces a security requirement that is not present in a basic CRUD flow: third-party credentials have to be persisted without treating them as ordinary application data.&lt;/p&gt;

&lt;p&gt;The project encrypts API keys before storing them in PostgreSQL using AES in GCM mode.&lt;/p&gt;

&lt;p&gt;The backend decrypts the value only when it needs to issue a request to the external provider.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenRouter API key
       |
       v
AES-GCM encryption
       |
       v
Encrypted database value
       |
       v
Backend decryption
       |
       v
External API request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping this responsibility on the server also prevents provider credentials from becoming part of the React application configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The React frontend
&lt;/h2&gt;

&lt;p&gt;The frontend is implemented with React 19 and TypeScript and built with Vite.&lt;/p&gt;

&lt;p&gt;The source tree is organized around a small set of responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;components/
pages/
context/
services/
types/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React Router handles navigation, while React Context is used for the authenticated user state.&lt;/p&gt;

&lt;p&gt;For the current size of the application, Context is sufficient. Introducing a larger state management solution would add an additional abstraction without solving a concrete problem.&lt;/p&gt;

&lt;p&gt;Most application state remains local to the page or component that owns it, while only genuinely shared state is lifted into the authentication context.&lt;/p&gt;




&lt;h2&gt;
  
  
  A dedicated API layer
&lt;/h2&gt;

&lt;p&gt;HTTP communication is centralized in a dedicated frontend service instead of being spread across individual components.&lt;/p&gt;

&lt;p&gt;That layer exposes operations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication;&lt;/li&gt;
&lt;li&gt;prompt CRUD operations;&lt;/li&gt;
&lt;li&gt;AI configuration;&lt;/li&gt;
&lt;li&gt;chat requests;&lt;/li&gt;
&lt;li&gt;administrative user management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps React components focused on rendering and interaction while request construction, credentials and common response handling remain in one place.&lt;/p&gt;

&lt;p&gt;It also creates a useful boundary if the backend API changes later: most HTTP-level changes can be isolated inside the service layer rather than propagated across the entire UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  The chat flow
&lt;/h2&gt;

&lt;p&gt;The chat page is where most parts of the system converge.&lt;/p&gt;

&lt;p&gt;A user can work with a saved prompt, provide additional context and send a conversation to the configured language model.&lt;/p&gt;

&lt;p&gt;React manages the interaction and conversation state, Spring Boot performs the authenticated server-side operation, PostgreSQL provides the user-specific configuration, and OpenRouter handles the model request.&lt;/p&gt;

&lt;p&gt;The resulting flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt
  +
User message
  |
  v
React chat interface
  |
  v
Spring Boot
  |
  v
OpenRouter
  |
  v
AI model response
  |
  v
React chat interface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is probably the part of the project that best represents the overall architecture because it crosses every major boundary of the application without coupling those layers together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I kept the architecture simple
&lt;/h2&gt;

&lt;p&gt;One of the main design decisions behind AI Prompt Lab was to avoid solving problems the application does not have.&lt;/p&gt;

&lt;p&gt;The backend is a single Spring Boot application because there is currently no domain or operational requirement that would justify distributing the system across multiple services.&lt;/p&gt;

&lt;p&gt;The frontend does not use Redux because authentication is the only significant global state and React Context already covers that requirement.&lt;/p&gt;

&lt;p&gt;The data model remains relational because the application's entities and ownership relationships map naturally to PostgreSQL.&lt;/p&gt;

&lt;p&gt;This is a principle I tend to apply regardless of the technology stack: &lt;strong&gt;introduce abstraction when it removes meaningful complexity, not simply because the abstraction exists.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A relatively small system with explicit boundaries is often easier to evolve than an over-engineered system whose infrastructure is more complex than its domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Possible extensions
&lt;/h2&gt;

&lt;p&gt;The current architecture leaves several natural directions for future development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt categories and tags
&lt;/h3&gt;

&lt;p&gt;Prompts could be grouped by domain, purpose or workflow, making larger collections easier to manage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt versioning
&lt;/h3&gt;

&lt;p&gt;Instead of replacing prompt content on every update, previous versions could be preserved and compared.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming responses
&lt;/h3&gt;

&lt;p&gt;The chat API could move from request-response communication to Server-Sent Events or another streaming mechanism so generated content appears progressively in the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple AI providers
&lt;/h3&gt;

&lt;p&gt;An internal provider abstraction could support OpenRouter, OpenAI, Anthropic or locally hosted models behind a common application interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversation persistence
&lt;/h3&gt;

&lt;p&gt;Chat sessions could be stored and reopened instead of existing only for the duration of the current frontend session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage metrics
&lt;/h3&gt;

&lt;p&gt;The backend could collect token usage, request latency and estimated cost per model, turning the application into a more complete prompt experimentation environment.&lt;/p&gt;




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

&lt;p&gt;AI Prompt Lab is a compact full-stack application that brings together several concerns that usually appear in real-world systems: persistence, authentication, authorization, secret handling, external HTTP integrations and a typed frontend.&lt;/p&gt;

&lt;p&gt;The backend uses &lt;strong&gt;Java 21 and Spring Boot&lt;/strong&gt; to provide the application and security layer, &lt;strong&gt;PostgreSQL&lt;/strong&gt; manages persistent state, &lt;strong&gt;React 19 and TypeScript&lt;/strong&gt; provide the user interface, and &lt;strong&gt;OpenRouter&lt;/strong&gt; connects the application to different language models.&lt;/p&gt;

&lt;p&gt;The project is intentionally not built around architectural novelty.&lt;/p&gt;

&lt;p&gt;Its design is based on keeping responsibilities explicit and introducing complexity only where the requirements justify it.&lt;/p&gt;

&lt;p&gt;For me, that is what makes the project interesting: frontend, backend, persistence, security and AI integration are treated as parts of the same system, while each layer remains responsible for a clearly defined concern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The complete source code is available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sfestacatenate/Java21_React19_AIPromptLab" rel="noopener noreferrer"&gt;Java21_React19_AIPromptLab on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://certosinolab.blogspot.com/2026/08/building-ai-prompt-lab-with-java-21.html" rel="noopener noreferrer"&gt;CertosinoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>react</category>
      <category>ai</category>
    </item>
    <item>
      <title>Next.js + NestJS: Useful Architecture or Unnecessary Complexity?</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Thu, 13 Aug 2026 12:06:48 +0000</pubDate>
      <link>https://dev.to/certosinolab/nextjs-nestjs-useful-architecture-or-unnecessary-complexity-4271</link>
      <guid>https://dev.to/certosinolab/nextjs-nestjs-useful-architecture-or-unnecessary-complexity-4271</guid>
      <description>&lt;p&gt;Next.js and NestJS are often compared as if you have to choose one.&lt;/p&gt;

&lt;p&gt;But that comparison is slightly misleading.&lt;/p&gt;

&lt;p&gt;Next.js is primarily a React framework that can also handle server-side logic, API endpoints, authentication, database access, and other backend tasks.&lt;/p&gt;

&lt;p&gt;NestJS, on the other hand, is a dedicated backend framework built around modules, dependency injection, controllers, services, queues, WebSockets, microservices, and structured application architecture.&lt;/p&gt;

&lt;p&gt;So the more interesting question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Next.js or NestJS?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When does a Next.js application actually need a separate NestJS backend?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For many applications, the answer is: it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with Next.js alone
&lt;/h2&gt;

&lt;p&gt;Imagine you're building a small SaaS application.&lt;/p&gt;

&lt;p&gt;You have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a React frontend&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;a database&lt;/li&gt;
&lt;li&gt;a few API endpoints&lt;/li&gt;
&lt;li&gt;payments&lt;/li&gt;
&lt;li&gt;some server-side business logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next.js can already handle this architecture quite well.&lt;/p&gt;

&lt;p&gt;For example, an API endpoint can live directly inside the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your architecture stays simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
Next.js
   ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is only one application to deploy, monitor and maintain.&lt;/p&gt;

&lt;p&gt;For small and medium-sized projects, this simplicity is a major advantage.&lt;/p&gt;

&lt;p&gt;Adding NestJS at this stage might simply create another network boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
Next.js
   ↓
NestJS
   ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have two applications, two deployment processes and an HTTP call between code that could have lived in the same process.&lt;/p&gt;

&lt;p&gt;That isn't automatically an improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  So when does NestJS start to make sense?
&lt;/h2&gt;

&lt;p&gt;The answer usually has less to do with the number of API endpoints and more to do with &lt;strong&gt;backend complexity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose your application starts doing more than serving requests from your Next.js frontend.&lt;/p&gt;

&lt;p&gt;You now have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js frontend
        ↓
      API
   ↙    ↓    ↘
Mobile  Jobs  Integrations
 App          External clients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, your backend is becoming a product of its own.&lt;/p&gt;

&lt;p&gt;That's where NestJS becomes much more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: Multiple clients use the same backend
&lt;/h2&gt;

&lt;p&gt;If Next.js is the only consumer of your server-side logic, keeping everything inside Next.js can be perfectly reasonable.&lt;/p&gt;

&lt;p&gt;But imagine that later you introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an iOS app&lt;/li&gt;
&lt;li&gt;an Android app&lt;/li&gt;
&lt;li&gt;an internal admin tool&lt;/li&gt;
&lt;li&gt;a public API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the backend shouldn't really belong to the Next.js application anymore.&lt;/p&gt;

&lt;p&gt;A dedicated NestJS API gives all clients the same backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        NestJS API
       /    |     \\
      /     |      \\
 Next.js  Mobile   Admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend becomes just one consumer among many.&lt;/p&gt;

&lt;p&gt;That separation now has a clear architectural reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: Background jobs become important
&lt;/h2&gt;

&lt;p&gt;Some tasks don't belong naturally inside a normal request-response cycle.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate 10,000 invoices
Process uploaded videos
Send 50,000 emails
Import a large CSV
Synchronize external systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These jobs may need queues, retries, workers, scheduling and monitoring.&lt;/p&gt;

&lt;p&gt;A dedicated backend architecture starts to become useful.&lt;/p&gt;

&lt;p&gt;Instead of trying to keep everything tied to your web frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js
   ↓
Request
   ↓
Long-running task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can move the work behind your API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js
   ↓
NestJS
   ↓
Queue
   ↓
Worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend can evolve independently from the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 3: Your business logic is getting complicated
&lt;/h2&gt;

&lt;p&gt;Consider an endpoint that originally looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first it might simply insert an order into the database.&lt;/p&gt;

&lt;p&gt;Later it needs to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Validate inventory
Calculate discounts
Reserve stock
Create payment
Send notifications
Update analytics
Publish an event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can absolutely organize all of this inside Next.js.&lt;/p&gt;

&lt;p&gt;The question is whether the backend is becoming large enough that a framework specifically designed around services, modules and dependency injection starts providing real value.&lt;/p&gt;

&lt;p&gt;NestJS encourages structures such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrdersModule
 ├── OrdersController
 ├── OrdersService
 ├── InventoryService
 ├── PaymentService
 └── OrdersRepository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a complex domain, that structure can become useful.&lt;/p&gt;

&lt;p&gt;For five simple CRUD endpoints, it may be unnecessary ceremony.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 4: The backend must scale independently
&lt;/h2&gt;

&lt;p&gt;Imagine your Next.js application receives normal web traffic, but your API performs CPU-intensive or high-volume workloads.&lt;/p&gt;

&lt;p&gt;If everything is deployed as one application, frontend and backend scaling are coupled.&lt;/p&gt;

&lt;p&gt;With separate applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js
Frontend instances: 3

NestJS
API instances: 10

Workers
Instances: 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;each part can scale according to its own workload.&lt;/p&gt;

&lt;p&gt;Again, this is valuable only when you actually have the problem.&lt;/p&gt;

&lt;p&gt;Separating applications because they &lt;em&gt;might&lt;/em&gt; need independent scaling someday usually means paying the complexity cost before receiving the benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden cost of adding NestJS
&lt;/h2&gt;

&lt;p&gt;A separate backend isn't free.&lt;/p&gt;

&lt;p&gt;You introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;another application&lt;/li&gt;
&lt;li&gt;another deployment&lt;/li&gt;
&lt;li&gt;another set of environment variables&lt;/li&gt;
&lt;li&gt;network communication&lt;/li&gt;
&lt;li&gt;authentication between systems&lt;/li&gt;
&lt;li&gt;CORS considerations&lt;/li&gt;
&lt;li&gt;duplicated types unless you share them&lt;/li&gt;
&lt;li&gt;additional logging and monitoring&lt;/li&gt;
&lt;li&gt;another place where failures can happen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That architectural boundary should give you something valuable in return.&lt;/p&gt;

&lt;p&gt;If it doesn't, it probably shouldn't exist yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple rule
&lt;/h2&gt;

&lt;p&gt;I like to think about it this way.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;Next.js alone&lt;/strong&gt; when your backend mostly exists to support your Next.js application.&lt;/p&gt;

&lt;p&gt;Consider &lt;strong&gt;Next.js + NestJS&lt;/strong&gt; when your backend starts becoming an independent system.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small website
→ Next.js

Typical SaaS
→ Probably Next.js

SaaS with complex domain logic
→ Maybe Next.js + NestJS

Web + mobile + public API
→ NestJS becomes much more interesting

Queues + workers + integrations
→ Dedicated backend starts making sense

Microservices / multiple backend teams
→ NestJS can be a natural fit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Next.js becoming capable of backend development doesn't make NestJS unnecessary.&lt;/p&gt;

&lt;p&gt;And NestJS being a powerful backend framework doesn't mean every Next.js application needs it.&lt;/p&gt;

&lt;p&gt;The key question is whether you actually need the architectural boundary.&lt;/p&gt;

&lt;p&gt;If your entire system can comfortably live inside one Next.js application, keeping it there may be the better engineering decision.&lt;/p&gt;

&lt;p&gt;When the backend develops its own clients, workloads, scaling requirements and domain complexity, extracting it into something like NestJS starts to make much more sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't add a second framework because the architecture looks more professional.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add it when the complexity it solves becomes greater than the complexity it introduces.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>nestjs</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>C# 15 Finally Gets Labeled `break` and `continue`</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Tue, 11 Aug 2026 22:03:04 +0000</pubDate>
      <link>https://dev.to/certosinolab/c-15-finally-gets-labeled-break-and-continue-1h1e</link>
      <guid>https://dev.to/certosinolab/c-15-finally-gets-labeled-break-and-continue-1h1e</guid>
      <description>&lt;p&gt;Nested loops are easy to write.&lt;/p&gt;

&lt;p&gt;Getting out of them cleanly isn't always as easy.&lt;/p&gt;

&lt;p&gt;Consider a simple grid search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;IsGoal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// How do we exit both loops?&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal &lt;code&gt;break&lt;/code&gt; isn't enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It only exits the &lt;strong&gt;innermost&lt;/strong&gt; loop.&lt;/p&gt;

&lt;p&gt;Until now, C# developers typically solved this with a Boolean flag, an early &lt;code&gt;return&lt;/code&gt;, or occasionally &lt;code&gt;goto&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C# 15 adds another option:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;labeled &lt;code&gt;break&lt;/code&gt; and &lt;code&gt;continue&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with a normal &lt;code&gt;break&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Suppose we're looking for a value inside a matrix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;But notice what &lt;code&gt;found&lt;/code&gt; is doing.&lt;/p&gt;

&lt;p&gt;It isn't really application state.&lt;/p&gt;

&lt;p&gt;It exists only to propagate a &lt;code&gt;break&lt;/code&gt; from one loop to another.&lt;/p&gt;

&lt;p&gt;With more deeply nested loops, this pattern becomes increasingly awkward.&lt;/p&gt;

&lt;h2&gt;
  
  
  C# 15: name the loop
&lt;/h2&gt;

&lt;p&gt;C# 15 lets us place a label directly on the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the important new syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of breaking only the inner loop, C# knows that we want to exit the loop labeled &lt;code&gt;outer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Execution continues immediately after that loop.&lt;/p&gt;

&lt;p&gt;The Boolean flag disappears completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  It also works with &lt;code&gt;continue&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The same idea applies to &lt;code&gt;continue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine processing a collection of orders.&lt;/p&gt;

&lt;p&gt;If any item in an order is invalid, we want to abandon that order and immediately move to the next one.&lt;/p&gt;

&lt;p&gt;Without labeled &lt;code&gt;continue&lt;/code&gt;, we might need another flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In C# 15:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ordersLoop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="n"&gt;ordersLoop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="n"&gt;ordersLoop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C# immediately starts the &lt;strong&gt;next iteration of the labeled outer loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There is no temporary flag and no additional condition after the inner loop.&lt;/p&gt;

&lt;p&gt;Microsoft's documentation specifically describes labeled jumps as a replacement for Boolean flags and some &lt;code&gt;goto&lt;/code&gt; patterns used to escape nested control flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about &lt;code&gt;goto&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;C# could already escape nested loops using &lt;code&gt;goto&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;Finished&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works, and &lt;code&gt;goto&lt;/code&gt; isn't inherently evil.&lt;/p&gt;

&lt;p&gt;But labeled &lt;code&gt;break&lt;/code&gt; communicates the intent more precisely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're not saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jump to this arbitrary position.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stop this specific loop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a useful distinction.&lt;/p&gt;

&lt;p&gt;C# 15 even introduces analyzer support through &lt;strong&gt;IDE0410&lt;/strong&gt;, which can identify some Boolean-flag and &lt;code&gt;goto&lt;/code&gt; patterns that can be replaced with labeled jumps.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;break&lt;/code&gt; and &lt;code&gt;continue&lt;/code&gt; aren't identical
&lt;/h2&gt;

&lt;p&gt;There is one small distinction worth remembering.&lt;/p&gt;

&lt;p&gt;A labeled &lt;code&gt;break&lt;/code&gt; can target an enclosing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for
foreach
while
do
switch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A labeled &lt;code&gt;continue&lt;/code&gt; can only target an enclosing &lt;strong&gt;loop&lt;/strong&gt;, because continuing a &lt;code&gt;switch&lt;/code&gt; doesn't make sense.&lt;/p&gt;

&lt;p&gt;And existing code doesn't change.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;still exits the nearest applicable construct.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;still continues the nearest loop.&lt;/p&gt;

&lt;p&gt;You only get the new behavior when you explicitly provide a label.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you start labeling every loop?
&lt;/h2&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;Most loops don't need it.&lt;/p&gt;

&lt;p&gt;And if you have five levels of nested control flow, extracting some logic into another method may still produce cleaner code.&lt;/p&gt;

&lt;p&gt;But there are legitimate cases where nested loops are the simplest representation of the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scanning matrices&lt;/li&gt;
&lt;li&gt;traversing multidimensional data&lt;/li&gt;
&lt;li&gt;processing batches and their items&lt;/li&gt;
&lt;li&gt;parsing nested structures&lt;/li&gt;
&lt;li&gt;searching combinations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those situations, labeled jumps can remove bookkeeping variables without forcing you to restructure otherwise straightforward code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it today
&lt;/h2&gt;

&lt;p&gt;C# 15 is currently available through the &lt;strong&gt;.NET 11 preview SDK&lt;/strong&gt; and Visual Studio 2026 Insiders.&lt;/p&gt;

&lt;p&gt;The feature itself is surprisingly small.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes language improvements aren't about enabling something completely new.&lt;/p&gt;

&lt;p&gt;They're about letting the code say more clearly what you already wanted it to do.&lt;/p&gt;

&lt;p&gt;And labeled &lt;code&gt;break&lt;/code&gt; and &lt;code&gt;continue&lt;/code&gt; are a nice example of exactly that.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Lazy Load Angular Services with `injectAsync()</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Tue, 11 Aug 2026 09:00:39 +0000</pubDate>
      <link>https://dev.to/certosinolab/lazy-load-angular-services-with-injectasync-c7h</link>
      <guid>https://dev.to/certosinolab/lazy-load-angular-services-with-injectasync-c7h</guid>
      <description>&lt;p&gt;Angular has always made it easy to lazy-load routes and components.&lt;/p&gt;

&lt;p&gt;But what about services?&lt;/p&gt;

&lt;p&gt;Imagine your application has an export feature that depends on a large library for generating spreadsheets or PDFs.&lt;/p&gt;

&lt;p&gt;Only a small percentage of users click &lt;strong&gt;Export&lt;/strong&gt;, but the code behind that feature may still end up being part of the initial JavaScript bundle.&lt;/p&gt;

&lt;p&gt;Angular 22 introduces a simple solution:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;injectAsync()&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It allows you to load a service only when you actually need it. The API is stable since Angular 22.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual approach
&lt;/h2&gt;

&lt;p&gt;Suppose we have a service responsible for exporting reports:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportExporter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate spreadsheet, PDF, etc.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, we could inject it into a component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReportExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./report-exporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button (click)="export()"&amp;gt;
      Export
    &amp;lt;/button&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ReportExporter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple and perfectly fine.&lt;/p&gt;

&lt;p&gt;But if &lt;code&gt;ReportExporter&lt;/code&gt; depends on a large library and the export feature is rarely used, we might prefer not to include that code in the initial load.&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;injectAsync()&lt;/code&gt; becomes useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter &lt;code&gt;injectAsync()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of importing the service normally, we can load it dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;injectAsync&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button (click)="export()"&amp;gt;
      Export
    &amp;lt;/button&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./report-exporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReportExporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./report-exporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReportExporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;injectAsync()&lt;/code&gt; doesn't immediately return the service.&lt;/p&gt;

&lt;p&gt;Instead, it returns a function that resolves to the service when called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call triggers the dynamic import.&lt;/p&gt;

&lt;p&gt;Angular's bundler can therefore place the service in a separate JavaScript chunk instead of loading it with the initial application bundle. Subsequent calls reuse the same promise, so the chunk isn't downloaded again.&lt;/p&gt;

&lt;p&gt;Conceptually, the loading process becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application starts
       ↓
Report page loads
       ↓
Exporter is NOT loaded
       ↓
User clicks Export
       ↓
Exporter chunk is downloaded
       ↓
Service is resolved through Angular DI
       ↓
Export runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For features that most users never activate, this can be a useful performance optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your service must be auto-provided
&lt;/h2&gt;

&lt;p&gt;There is one important requirement.&lt;/p&gt;

&lt;p&gt;For lazy injection to work, Angular needs to know how to create the service automatically.&lt;/p&gt;

&lt;p&gt;You can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportExporter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or Angular's newer &lt;code&gt;@Service()&lt;/code&gt; decorator:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportExporter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service that isn't auto-provided cannot be lazy-loaded with &lt;code&gt;injectAsync()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can also prefetch it
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't want the service in the initial bundle, but you also don't want the user to wait after clicking the button.&lt;/p&gt;

&lt;p&gt;Angular provides a useful middle ground.&lt;/p&gt;

&lt;p&gt;You can prefetch the dependency when the browser becomes idle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;injectAsync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onIdle&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./report-exporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReportExporter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;prefetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;onIdle&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can load normally first.&lt;/p&gt;

&lt;p&gt;Then, when the browser has some idle time, Angular can start downloading the lazy dependency in the background.&lt;/p&gt;

&lt;p&gt;If the user needs the feature before the prefetch happens, Angular simply loads it immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use it?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;injectAsync()&lt;/code&gt; doesn't mean that every Angular service should become lazy.&lt;/p&gt;

&lt;p&gt;For a small service used throughout the application, regular &lt;code&gt;inject()&lt;/code&gt; is still simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;injectAsync()&lt;/code&gt; becomes more interesting when a service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;depends on a large third-party library&lt;/li&gt;
&lt;li&gt;powers a rarely used feature&lt;/li&gt;
&lt;li&gt;handles exports or document generation&lt;/li&gt;
&lt;li&gt;loads specialized editors or visualization tools&lt;/li&gt;
&lt;li&gt;performs work that only some users need&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, use it when loading the service later can actually save meaningful JavaScript during application startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;injectAsync()&lt;/code&gt; is a small Angular 22 API, but it fills an interesting gap.&lt;/p&gt;

&lt;p&gt;We already lazy-load routes.&lt;/p&gt;

&lt;p&gt;We already lazy-load components.&lt;/p&gt;

&lt;p&gt;Now we can apply the same idea to services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./report-exporter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And load expensive functionality only when the user actually needs it.&lt;/p&gt;

&lt;p&gt;Sometimes improving startup performance isn't about making code execute faster.&lt;/p&gt;

&lt;p&gt;It's simply about &lt;strong&gt;not loading that code yet&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Sync Two Browser Tabs with JavaScript</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Mon, 10 Aug 2026 06:03:22 +0000</pubDate>
      <link>https://dev.to/certosinolab/how-to-sync-two-browser-tabs-with-javascript-108l</link>
      <guid>https://dev.to/certosinolab/how-to-sync-two-browser-tabs-with-javascript-108l</guid>
      <description>&lt;p&gt;Have you ever opened the same web app in two browser tabs and noticed that they behave like two completely separate applications?&lt;/p&gt;

&lt;p&gt;Sometimes that's fine.&lt;/p&gt;

&lt;p&gt;But sometimes you want an action in one tab to be immediately reflected in the others.&lt;/p&gt;

&lt;p&gt;You could use a server, WebSockets, polling, or even &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But for simple communication between browser tabs, JavaScript already provides a native API:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;BroadcastChannel&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how it works by building a very small counter that stays synchronized across multiple tabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTML
&lt;/h2&gt;

&lt;p&gt;We only need a counter and a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+1&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing special so far.&lt;/p&gt;

&lt;p&gt;Each browser tab will have its own JavaScript state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without any synchronization, clicking the button in one tab would only update that tab.&lt;/p&gt;

&lt;p&gt;Let's fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a BroadcastChannel
&lt;/h2&gt;

&lt;p&gt;First, create a channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string &lt;code&gt;"counter"&lt;/code&gt; is simply the name of the channel.&lt;/p&gt;

&lt;p&gt;Any tab from the same origin that creates a &lt;code&gt;BroadcastChannel&lt;/code&gt; with the same name can communicate with the others.&lt;/p&gt;

&lt;p&gt;Now let's implement our counter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time we click the button, we increase the counter and then send the new value through the channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the other tabs still need to listen for that message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receive messages from other tabs
&lt;/h2&gt;

&lt;p&gt;We can do that with the &lt;code&gt;message&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's basically the whole implementation.&lt;/p&gt;

&lt;p&gt;Here is the complete JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open the page in two browser tabs.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;+1&lt;/strong&gt; in the first tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tab 1 → 1
Tab 2 → 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then click &lt;strong&gt;+1&lt;/strong&gt; in the second tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tab 1 → 2
Tab 2 → 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two tabs are now communicating directly.&lt;/p&gt;

&lt;p&gt;No server.&lt;/p&gt;

&lt;p&gt;No WebSocket.&lt;/p&gt;

&lt;p&gt;No polling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually happening?
&lt;/h2&gt;

&lt;p&gt;When this line runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the browser sends the value to the other contexts listening to the &lt;code&gt;"counter"&lt;/code&gt; channel.&lt;/p&gt;

&lt;p&gt;The other tab receives it here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// data contains the received value&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important detail is that a channel does &lt;strong&gt;not&lt;/strong&gt; send the message back to the same &lt;code&gt;BroadcastChannel&lt;/code&gt; object that posted it.&lt;/p&gt;

&lt;p&gt;That's why we update the current tab ourselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and use &lt;code&gt;BroadcastChannel&lt;/code&gt; to update the other tabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can send more than numbers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BroadcastChannel&lt;/code&gt; isn't limited to strings or numbers.&lt;/p&gt;

&lt;p&gt;You can send objects too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter-updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And receive them normally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter-updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For real applications, this approach is often better because different message types can share the same channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same origin only
&lt;/h2&gt;

&lt;p&gt;There is one important restriction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BroadcastChannel&lt;/code&gt; works between browsing contexts that belong to the &lt;strong&gt;same origin&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, two tabs opened on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can communicate with each other.&lt;/p&gt;

&lt;p&gt;A page on another domain cannot join that channel.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;BroadcastChannel&lt;/code&gt; useful for communication between different instances of the same web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is this useful?
&lt;/h2&gt;

&lt;p&gt;A synchronized counter is obviously just a demo.&lt;/p&gt;

&lt;p&gt;In a real application, the same technique can be used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refresh data after another tab modifies it&lt;/li&gt;
&lt;li&gt;synchronize application state&lt;/li&gt;
&lt;li&gt;notify tabs when cached data becomes stale&lt;/li&gt;
&lt;li&gt;coordinate background operations&lt;/li&gt;
&lt;li&gt;communicate between tabs, windows and Web Workers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simple cross-tab communication, it can save you from building a much more complicated solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the channel
&lt;/h2&gt;

&lt;p&gt;When you no longer need the channel, you can close it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For channels that exist for the entire lifetime of a page, this may not be especially important.&lt;/p&gt;

&lt;p&gt;But it's useful when channels are created dynamically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BroadcastChannel&lt;/code&gt; is one of those small Web APIs that is easy to overlook.&lt;/p&gt;

&lt;p&gt;The entire idea comes down to two operations.&lt;/p&gt;

&lt;p&gt;Send something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Receive something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// react to the message&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the next time you need two tabs of the same web app to communicate, you might not need a server or WebSocket connection.&lt;/p&gt;

&lt;p&gt;Sometimes the browser already gives you exactly what you need.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>JavaScript Finally Fixed Summation — But 0.1 + 0.2 Is Still Broken</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Fri, 07 Aug 2026 03:24:15 +0000</pubDate>
      <link>https://dev.to/certosinolab/javascript-finally-fixed-summation-but-01-02-is-still-broken-gnn</link>
      <guid>https://dev.to/certosinolab/javascript-finally-fixed-summation-but-01-02-is-still-broken-gnn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq6rfn1r5nfef5f86ixr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq6rfn1r5nfef5f86ixr1.png" alt="Comparison between ordinary JavaScript summation and Math.sumPrecise showing intermediate floating-point precision loss" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've written JavaScript for more than five minutes, you've probably seen this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;span class="c1"&gt;// 0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has become one of those JavaScript jokes we all know.&lt;/p&gt;

&lt;p&gt;But in 2026, JavaScript got a new method that sounds like it should finally solve the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So... is floating-point pain finally over?&lt;/p&gt;

&lt;p&gt;Not exactly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; solves a real and surprisingly common precision problem, but probably not the one you're thinking about.&lt;/p&gt;

&lt;p&gt;And understanding the difference tells us something important about how JavaScript numbers actually work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with the way we usually sum numbers
&lt;/h2&gt;

&lt;p&gt;Let's say we want to add a list of numbers.&lt;/p&gt;

&lt;p&gt;Most JavaScript developers would probably reach for &lt;code&gt;reduce()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing wrong here.&lt;/p&gt;

&lt;p&gt;For ordinary numbers, this works exactly as expected.&lt;/p&gt;

&lt;p&gt;But now let's make things more interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100000000000000000000
+                  0.1
-100000000000000000000
--------------------------------
                   0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer should be &lt;code&gt;0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;JavaScript gives us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is exactly the kind of problem &lt;code&gt;Math.sumPrecise()&lt;/code&gt; was created to address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meet &lt;code&gt;Math.sumPrecise()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The same calculation can now be written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// 0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;No library.&lt;/p&gt;

&lt;p&gt;No custom summation algorithm.&lt;/p&gt;

&lt;p&gt;No special class.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method accepts an iterable of JavaScript numbers and returns their sum while avoiding the precision loss that can happen during intermediate additions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; was standardized as part of ECMAScript 2026 and became Baseline Newly Available across modern browsers in April 2026.&lt;/p&gt;

&lt;p&gt;But to understand why this matters, we need to look at what went wrong with &lt;code&gt;reduce()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why did &lt;code&gt;reduce()&lt;/code&gt; return zero?
&lt;/h2&gt;

&lt;p&gt;JavaScript's regular &lt;code&gt;Number&lt;/code&gt; type uses IEEE 754 double-precision floating-point numbers.&lt;/p&gt;

&lt;p&gt;That means numbers have a limited amount of precision.&lt;/p&gt;

&lt;p&gt;Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;1e20&lt;/code&gt; is enormous compared with &lt;code&gt;0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At that magnitude, JavaScript cannot represent the tiny difference introduced by adding &lt;code&gt;0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is effectively stored as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;0.1&lt;/code&gt; has disappeared.&lt;/p&gt;

&lt;p&gt;Then our reduction continues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the precision was lost &lt;strong&gt;during an intermediate calculation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once that information is gone, the next addition can't magically recover it.&lt;/p&gt;

&lt;p&gt;This is why the order in which floating-point operations happen can sometimes affect the result of a naive summation.&lt;/p&gt;




&lt;h2&gt;
  
  
  What &lt;code&gt;Math.sumPrecise()&lt;/code&gt; does differently
&lt;/h2&gt;

&lt;p&gt;Conceptually, instead of repeatedly doing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current sum
    +
next number
    ↓
round
    ↓
current sum
    +
next number
    ↓
round
    ↓
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; performs the summation in a way that preserves the contributions of the input values much more accurately before producing the final JavaScript &lt;code&gt;Number&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;MDN describes the behavior roughly as if the exact mathematical values represented by the input floating-point numbers were summed first, with the final result then converted to the nearest representable 64-bit floating-point value.&lt;/p&gt;

&lt;p&gt;Compare them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 0&lt;/span&gt;

&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with integers of dramatically different magnitudes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000_000_000&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 0&lt;/span&gt;

&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a meaningful improvement.&lt;/p&gt;

&lt;p&gt;But now comes the interesting part.&lt;/p&gt;




&lt;h2&gt;
  
  
  Does it fix &lt;code&gt;0.1 + 0.2&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Let's try it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yep.&lt;/p&gt;

&lt;p&gt;Our favorite JavaScript number is still alive.&lt;/p&gt;

&lt;p&gt;So what happened?&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;Math.sumPrecise()&lt;/code&gt; can't fix numbers that were already approximate
&lt;/h2&gt;

&lt;p&gt;The famous &lt;code&gt;0.1 + 0.2&lt;/code&gt; issue is slightly different from our previous example.&lt;/p&gt;

&lt;p&gt;The problem begins &lt;strong&gt;before the addition even happens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Numbers such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.1
0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cannot be represented exactly using binary floating point.&lt;/p&gt;

&lt;p&gt;In the same way that &lt;code&gt;1 / 3&lt;/code&gt; cannot be represented exactly with a finite number of decimal digits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.333333333333...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;certain decimal fractions cannot be represented exactly as finite binary fractions.&lt;/p&gt;

&lt;p&gt;So when you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the value stored by JavaScript is already the closest representable floating-point approximation.&lt;/p&gt;

&lt;p&gt;The same happens with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; can accurately add the floating-point values it receives.&lt;/p&gt;

&lt;p&gt;But it cannot turn those values back into the exact decimal numbers you originally had in mind.&lt;/p&gt;

&lt;p&gt;That's why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;still produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is probably the most important thing to understand about the new API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; fixes &lt;strong&gt;precision lost while summing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does not fix &lt;strong&gt;precision already lost when representing a number&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  So when is &lt;code&gt;Math.sumPrecise()&lt;/code&gt; actually useful?
&lt;/h2&gt;

&lt;p&gt;Consider a dashboard collecting thousands of measurements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;measurements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;1200000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.00021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.00017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1200000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.00034&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or scientific data where values can have dramatically different magnitudes.&lt;/p&gt;

&lt;p&gt;Or analytics where you're aggregating a large iterable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or perhaps you're processing a generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;measurements&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;measurements&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// 0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;Math.sumPrecise()&lt;/code&gt; doesn't require an array.&lt;/p&gt;

&lt;p&gt;It accepts an &lt;strong&gt;iterable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means things such as arrays, sets, typed arrays, and generators can be passed directly when they yield numbers.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Float64Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes the API pleasantly small and composable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Should we stop using &lt;code&gt;reduce()&lt;/code&gt; for sums?
&lt;/h2&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;isn't suddenly bad code.&lt;/p&gt;

&lt;p&gt;For many applications, especially when you're adding ordinary integers or values with similar magnitudes, the result will be perfectly fine.&lt;/p&gt;

&lt;p&gt;But there is now a more expressive operation when what you actually mean is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Give me the most accurate sum of these JavaScript numbers.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of implementing summation using a generic reduction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can express your intention directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like APIs like this.&lt;/p&gt;

&lt;p&gt;Not because they make JavaScript dramatically more powerful, but because they move a surprisingly subtle problem into the language itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  One important warning: money
&lt;/h2&gt;

&lt;p&gt;You might see the word &lt;strong&gt;precise&lt;/strong&gt; and immediately think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Great. I'll use this for prices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Be careful.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// 0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; turn JavaScript Numbers into decimal numbers.&lt;/p&gt;

&lt;p&gt;If your application requires exact decimal arithmetic — especially financial calculations — the underlying binary floating-point representation is still relevant.&lt;/p&gt;

&lt;p&gt;A common approach for currencies is to store values using the smallest unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pricesInCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;35&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalInCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pricesInCents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalInCents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 65&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then convert for display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalInDollars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;totalInCents&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the requirements of the application, you may instead need &lt;code&gt;BigInt&lt;/code&gt; or a dedicated decimal arithmetic solution.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; makes summation better.&lt;/p&gt;

&lt;p&gt;It does not change JavaScript's numeric model.&lt;/p&gt;




&lt;h2&gt;
  
  
  A couple of surprising edge cases
&lt;/h2&gt;

&lt;p&gt;There are some details worth knowing.&lt;/p&gt;

&lt;p&gt;An empty iterable returns negative zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

&lt;span class="c1"&gt;// -0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, JavaScript has both &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;-0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can verify it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([]),&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, &lt;code&gt;Math.sumPrecise()&lt;/code&gt; expects numbers.&lt;/p&gt;

&lt;p&gt;This doesn't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It throws a &lt;code&gt;TypeError&lt;/code&gt; instead of coercing &lt;code&gt;"2"&lt;/code&gt; into the number &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's actually a nice property.&lt;/p&gt;

&lt;p&gt;A function whose purpose is numerical accuracy probably shouldn't silently start converting random strings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Can I use it today?
&lt;/h2&gt;

&lt;p&gt;As of 2026, yes — with the usual compatibility caveat.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt; became &lt;strong&gt;Baseline Newly Available in April 2026&lt;/strong&gt;, meaning current releases of the major browser engines support it, although older browsers and devices may not.&lt;/p&gt;

&lt;p&gt;If your application supports older environments, feature detection is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sumPrecise&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also polyfill implementations available through projects such as core-js.&lt;/p&gt;

&lt;p&gt;Whether you should ship a fallback depends, as always, on the browsers your users actually run.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;reduce()&lt;/code&gt; vs &lt;code&gt;Math.sumPrecise()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here's the mental model I would keep:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;reduce((a, b) =&amp;gt; a + b)&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Math.sumPrecise()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple sums&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works with floating-point numbers&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimizes intermediate precision loss&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixes &lt;code&gt;0.1 + 0.2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accepts iterables directly&lt;/td&gt;
&lt;td&gt;Depends&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact decimal arithmetic&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communicates “sum these values” directly&lt;/td&gt;
&lt;td&gt;Kind of&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most important row is probably this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fixes 0.1 + 0.2 → NO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the name &lt;code&gt;sumPrecise()&lt;/code&gt; makes it very easy to assume otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  JavaScript didn't fix floating point. It fixed summation.
&lt;/h2&gt;

&lt;p&gt;And I think that's the right way to think about this feature.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not going anywhere.&lt;/p&gt;

&lt;p&gt;But this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no longer has to be the best JavaScript can do.&lt;/p&gt;

&lt;p&gt;We can now write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sumPrecise&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e20&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// 0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a small API.&lt;/p&gt;

&lt;p&gt;Probably not one that will change the way you write JavaScript every day.&lt;/p&gt;

&lt;p&gt;But it's also the kind of language improvement I enjoy: taking something deceptively difficult, giving it a clear name, and making the correct solution a one-liner.&lt;/p&gt;

&lt;p&gt;So no, JavaScript hasn't finally defeated floating point.&lt;/p&gt;

&lt;p&gt;But it did quietly become a lot better at adding numbers.&lt;/p&gt;

&lt;p&gt;And honestly, I'll take that.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>ecmascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Streaming Large ZIP Archives in the Browser with Vue and the File System Access API</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Thu, 30 Jul 2026 17:32:27 +0000</pubDate>
      <link>https://dev.to/certosinolab/streaming-large-zip-archives-in-the-browser-with-vue-and-the-file-system-access-api-42ng</link>
      <guid>https://dev.to/certosinolab/streaming-large-zip-archives-in-the-browser-with-vue-and-the-file-system-access-api-42ng</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8al5zqx20vu5qnaug3cd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8al5zqx20vu5qnaug3cd.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Blob vs streaming, ZIP64, progress tracking, cancellation, and browser fallbacks&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Exporting a file from a web application usually looks like a simple operation.&lt;/p&gt;

&lt;p&gt;The application generates some data, creates a &lt;code&gt;Blob&lt;/code&gt;, builds an object URL, and programmatically clicks a download link.&lt;/p&gt;

&lt;p&gt;This approach works well for reports, JSON documents, generated images, and other relatively small outputs. However, it becomes much more problematic when the application needs to export hundreds of megabytes or several gigabytes of data.&lt;/p&gt;

&lt;p&gt;A large archive may require the browser to keep source files, compression buffers, temporary byte arrays, and the final &lt;code&gt;Blob&lt;/code&gt; alive at the same time. On a device with limited memory, this can slow down the page, trigger aggressive garbage collection, or terminate the browser tab completely.&lt;/p&gt;

&lt;p&gt;To explore a different approach, I built &lt;strong&gt;StreamZIP Lab&lt;/strong&gt;, a small frontend project that creates ZIP archives directly in the browser.&lt;/p&gt;

&lt;p&gt;The application compares two export strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;writing the ZIP archive progressively to disk through the File System Access API;&lt;/li&gt;
&lt;li&gt;generating the complete archive as a &lt;code&gt;Blob&lt;/code&gt; before starting a traditional browser download.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project also covers ZIP64, compression strategies, progress tracking, cancellation, browser compatibility, and the special treatment of formats such as JPEG and MP4.&lt;/p&gt;

&lt;p&gt;Everything is implemented in three files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;streamzip-lab/
├── index.html
├── styles.css
└── functions.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue is loaded directly from a CDN, so there is no package manager, bundler, build command, or Single-File Component involved.&lt;/p&gt;

&lt;p&gt;This article explains the technical decisions behind the project and some of the less obvious problems involved in generating large archives inside a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the project does
&lt;/h2&gt;

&lt;p&gt;StreamZIP Lab allows the user to select multiple local files and export them as a single ZIP archive.&lt;/p&gt;

&lt;p&gt;The main workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user selects or drops one or more files.&lt;/li&gt;
&lt;li&gt;The application examines their extensions and MIME types.&lt;/li&gt;
&lt;li&gt;Each file is assigned either the &lt;code&gt;DEFLATE&lt;/code&gt; or &lt;code&gt;STORE&lt;/code&gt; strategy.&lt;/li&gt;
&lt;li&gt;The user chooses between streaming and Blob output.&lt;/li&gt;
&lt;li&gt;The ZIP archive is generated.&lt;/li&gt;
&lt;li&gt;The interface displays per-file and global progress.&lt;/li&gt;
&lt;li&gt;The export can be cancelled while it is running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application also shows information about the current browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Secure context       Yes
showSaveFilePicker   Available
Web Streams          Available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the File System Access API is supported, the recommended mode is &lt;strong&gt;Stream to disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When it is not available, the application automatically switches to &lt;strong&gt;Blob + download&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The project is related to Progressive Web Apps because direct file interaction is especially useful for installable editors, media tools, local-first applications, backup utilities, and offline-capable applications.&lt;/p&gt;

&lt;p&gt;However, the current three-file demo is not, by itself, a complete installable PWA. A production PWA would also require at least a web app manifest and a service worker. StreamZIP Lab focuses specifically on the export and filesystem layer that could be integrated into a larger PWA.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technology stack
&lt;/h2&gt;

&lt;p&gt;The project uses only standard browser technologies and two external libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue 3 for the reactive interface;&lt;/li&gt;
&lt;li&gt;zip.js for ZIP creation;&lt;/li&gt;
&lt;li&gt;the File System Access API for direct disk output;&lt;/li&gt;
&lt;li&gt;the Web Streams API for progressive writing;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AbortController&lt;/code&gt; for cancellation;&lt;/li&gt;
&lt;li&gt;the traditional Blob download pattern as a fallback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vue is loaded through its global production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/vue@3.5.40/dist/vue.global.prod.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;zip.js is loaded in the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/dist/zip-full.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the project logic is loaded from the local JavaScript file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"functions.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue officially supports this CDN-based approach. The global build exposes APIs such as &lt;code&gt;createApp&lt;/code&gt; through the global &lt;code&gt;Vue&lt;/code&gt; object and does not require a build step. The main limitation is that Single-File Component syntax is not available.&lt;/p&gt;

&lt;p&gt;For a small technical lab, this limitation is acceptable. Keeping the project in three readable files makes it easier to inspect the actual browser APIs without introducing build configuration.&lt;/p&gt;

&lt;p&gt;The Vue application starts with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="na"&gt;archiveName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamzip-export.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exportMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;compressionMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;zip64Mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the interface state is stored in one application object.&lt;/p&gt;

&lt;p&gt;For a larger application, I would probably divide the code into modules or Vue components. For this project, keeping the complete workflow in &lt;code&gt;functions.js&lt;/code&gt; makes the relationship between state, browser APIs, and ZIP generation easier to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The traditional Blob export pattern
&lt;/h2&gt;

&lt;p&gt;A common client-side download implementation looks similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;generatedData&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/octet-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;export.bin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern can be used for a ZIP archive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobWriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZipWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blobWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document.txt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archiveBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after &lt;code&gt;close()&lt;/code&gt; returns the finished &lt;code&gt;Blob&lt;/code&gt; can the browser download it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;downloadBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;archiveBlob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is simple and compatible with many browsers.&lt;/p&gt;

&lt;p&gt;The important detail is that the application needs to finish generating the archive before the download starts.&lt;/p&gt;

&lt;p&gt;For a 5 MB archive, this is normally not a problem.&lt;/p&gt;

&lt;p&gt;For a 5 GB archive, it may become the central architectural problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Blob is not exactly the same as memory
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;Blob&lt;/code&gt; represents immutable raw data. It provides metadata such as its size and MIME type and can expose slices or a readable stream.&lt;/p&gt;

&lt;p&gt;It is tempting to say that every Blob always exists entirely in RAM, but that explanation is too simplistic.&lt;/p&gt;

&lt;p&gt;A browser implementation may internally store Blob data in memory, temporary files, shared buffers, or a combination of these strategies. The exact storage mechanism is an implementation detail.&lt;/p&gt;

&lt;p&gt;The real problem is the complete generation pipeline.&lt;/p&gt;

&lt;p&gt;When an archive is produced as one final Blob, several resources may coexist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source File objects
        ↓
Reader buffers
        ↓
Compression worker buffers
        ↓
Compressed chunks
        ↓
ZIP output buffers
        ↓
Final Blob
        ↓
Object URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional copies can appear when application code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concatenates arrays;&lt;/li&gt;
&lt;li&gt;converts streams to &lt;code&gt;ArrayBuffer&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;constructs large &lt;code&gt;Uint8Array&lt;/code&gt; values;&lt;/li&gt;
&lt;li&gt;calls &lt;code&gt;Response.blob()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;duplicates data between workers and the main thread;&lt;/li&gt;
&lt;li&gt;retains references longer than necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final memory peak depends on the browser, the ZIP library, compression settings, file formats, worker implementation, and garbage collection timing.&lt;/p&gt;

&lt;p&gt;Therefore, StreamZIP Lab does not claim that a particular input size will always crash a browser.&lt;/p&gt;

&lt;p&gt;Instead, it introduces a configurable warning threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BYTES_IN_MIB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BLOB_WARNING_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;BYTES_IN_MIB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the selected files exceed 512 MiB and Blob mode is active, the application shows a warning and requires explicit confirmation.&lt;/p&gt;

&lt;p&gt;This is a user-interface safety threshold, not a browser specification limit.&lt;/p&gt;

&lt;p&gt;Some devices may handle a much larger archive. Others may struggle with a smaller one, especially on mobile devices or when several memory-intensive tabs are open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why streaming changes the architecture
&lt;/h2&gt;

&lt;p&gt;The Web Streams API is designed for data that is created, processed, and consumed incrementally instead of being read entirely into memory. It also provides queuing and backpressure mechanisms.&lt;/p&gt;

&lt;p&gt;A streaming ZIP pipeline can be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File
  ↓
BlobReader
  ↓
Compression
  ↓
ZIP records
  ↓
WritableStream
  ↓
Destination file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of waiting for one complete archive object, the ZIP writer emits chunks as they become available.&lt;/p&gt;

&lt;p&gt;Each chunk is passed to the destination stream and written to disk.&lt;/p&gt;

&lt;p&gt;The main conceptual difference is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blob mode
Read everything
→ generate everything
→ retain final result
→ begin download
Streaming mode
Read a chunk
→ process the chunk
→ write the chunk
→ continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streaming does not mean zero memory usage.&lt;/p&gt;

&lt;p&gt;The browser and compression library still require buffers. ZIP metadata must also be retained until the central directory can be written.&lt;/p&gt;

&lt;p&gt;The objective is to keep memory usage related to the active working set rather than the complete archive size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backpressure
&lt;/h2&gt;

&lt;p&gt;Backpressure is one of the most important properties of a streaming pipeline.&lt;/p&gt;

&lt;p&gt;Imagine that the ZIP compressor can produce data faster than the filesystem can write it.&lt;/p&gt;

&lt;p&gt;Without flow control, compressed chunks would continue accumulating in memory.&lt;/p&gt;

&lt;p&gt;A stream communicates that the destination is temporarily unable to accept more data. This signal moves backward through the pipeline, slowing down the producer.&lt;/p&gt;

&lt;p&gt;The Streams Standard describes backpressure as the process of normalizing the flow according to the rate at which the chain can process chunks.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fast producer
     ↓
Compression
     ↓
Full queue
     ↓
Slow filesystem
The destination applies backpressure
     ↑
The producer slows down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In application code, this is often handled by the stream implementation and the promises returned by write operations.&lt;/p&gt;

&lt;p&gt;StreamZIP Lab passes a &lt;code&gt;WritableStream&lt;/code&gt; to zip.js. The library then writes ZIP data to that stream while respecting the asynchronous destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opening the save picker
&lt;/h2&gt;

&lt;p&gt;The streaming export starts with &lt;code&gt;showSaveFilePicker()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fileHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showSaveFilePicker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamzip-lab-export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;suggestedName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ZIP archive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method opens the native save dialog and returns a &lt;code&gt;FileSystemFileHandle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The options do not silently force the final location or filename. They provide hints to the browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;suggestedName&lt;/code&gt; proposes a default filename;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; describes the selectable format;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;accept&lt;/code&gt; associates a MIME type with one or more extensions;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; allows the browser to associate similar picker operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The File System Access specification recommends providing both MIME types and extensions because operating systems do not all identify file formats in the same way.&lt;/p&gt;

&lt;p&gt;The API does not give the page unrestricted access to arbitrary filesystem paths.&lt;/p&gt;

&lt;p&gt;The user sees a browser-controlled or operating-system-controlled picker and selects the destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  User activation must be preserved
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;showSaveFilePicker()&lt;/code&gt; is a privileged operation.&lt;/p&gt;

&lt;p&gt;It must be initiated from a user action such as a button click.&lt;/p&gt;

&lt;p&gt;This requirement affects the order of the code.&lt;/p&gt;

&lt;p&gt;The picker should be opened before long asynchronous work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;exportWithFileSystemAccess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Keep this as the first await in the export path.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showSaveFilePicker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;suggestedName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Compression can begin after authorization.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createArchive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileHandle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A problematic implementation could do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;exportArchive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;performLongCalculation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showSaveFilePicker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;suggestedName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the time the long calculation finishes, the transient user activation may no longer be valid.&lt;/p&gt;

&lt;p&gt;The picker can then fail with a security-related error.&lt;/p&gt;

&lt;p&gt;For this reason, StreamZIP Lab keeps &lt;code&gt;showSaveFilePicker()&lt;/code&gt; as the first awaited operation in the native streaming path.&lt;/p&gt;

&lt;p&gt;The application first asks the user where the archive should be saved. Only after the handle has been obtained does it begin compression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the writable destination
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;FileSystemFileHandle&lt;/code&gt; does not write bytes by itself.&lt;/p&gt;

&lt;p&gt;The application calls &lt;code&gt;createWritable()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeWritable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fileHandle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWritable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a writable file stream connected to the selected destination.&lt;/p&gt;

&lt;p&gt;The project stores a reference to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeNativeWritable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nativeWritable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reference is needed because cancellation must be able to abort the destination even while zip.js is using an outer stream.&lt;/p&gt;

&lt;p&gt;The project creates a small adapter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WritableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nativeWritable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nativeWritable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nativeWritable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter exposes the three relevant operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;write()&lt;/code&gt; forwards a generated ZIP chunk;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;close()&lt;/code&gt; finalizes the destination;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;abort()&lt;/code&gt; interrupts the write operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ZIP writer is then created using this stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createZipWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, zip.js can progressively send archive records to the selected file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the ZIP writer
&lt;/h2&gt;

&lt;p&gt;The project centralizes ZIP writer configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createZipWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bufferedWrite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dataDescriptorSignature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip64Mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;force&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZipWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bufferedWrite: false&lt;/code&gt; is important for the purpose of the demo because the destination should receive data progressively instead of waiting for a fully buffered output.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dataDescriptorSignature: true&lt;/code&gt; tells the writer to include a signature before data descriptors.&lt;/p&gt;

&lt;p&gt;When ZIP64 is forced, &lt;code&gt;zip64&lt;/code&gt; is added to the options. Otherwise, zip.js can determine when ZIP64 structures are necessary.&lt;/p&gt;

&lt;p&gt;zip.js is designed for large data sets and supports Web Streams, multi-core compression, and archives larger than 4 GB through ZIP64.&lt;/p&gt;

&lt;p&gt;Using a library is important here.&lt;/p&gt;

&lt;p&gt;A valid ZIP writer must handle much more than concatenating compressed files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local file headers;&lt;/li&gt;
&lt;li&gt;compression methods;&lt;/li&gt;
&lt;li&gt;CRC-32 values;&lt;/li&gt;
&lt;li&gt;compressed and uncompressed sizes;&lt;/li&gt;
&lt;li&gt;file names and encodings;&lt;/li&gt;
&lt;li&gt;timestamps;&lt;/li&gt;
&lt;li&gt;offsets;&lt;/li&gt;
&lt;li&gt;data descriptors;&lt;/li&gt;
&lt;li&gt;central directory entries;&lt;/li&gt;
&lt;li&gt;end-of-directory records;&lt;/li&gt;
&lt;li&gt;ZIP64 extra fields and records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing all of this correctly would turn the project into a ZIP format implementation rather than a File System Access API experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  General structure of a ZIP archive
&lt;/h2&gt;

&lt;p&gt;A ZIP archive is not simply a sequence of compressed files.&lt;/p&gt;

&lt;p&gt;A simplified archive with two files looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local file header: document.txt
Compressed or stored data
Optional data descriptor
Local file header: photo.jpg
Compressed or stored data
Optional data descriptor
Central directory entry: document.txt
Central directory entry: photo.jpg
Optional ZIP64 end record
Optional ZIP64 locator
End of central directory record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official ZIP specification describes the overall structure as local file records followed by the central directory and the end-of-central-directory structures. It also explicitly states that data descriptors facilitate streaming.&lt;/p&gt;

&lt;p&gt;Each local file header appears near the corresponding file data.&lt;/p&gt;

&lt;p&gt;It contains information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the compression method;&lt;/li&gt;
&lt;li&gt;modification time;&lt;/li&gt;
&lt;li&gt;file name;&lt;/li&gt;
&lt;li&gt;general-purpose flags;&lt;/li&gt;
&lt;li&gt;CRC-32;&lt;/li&gt;
&lt;li&gt;compressed size;&lt;/li&gt;
&lt;li&gt;uncompressed size;&lt;/li&gt;
&lt;li&gt;optional extra fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The central directory is written near the end of the archive.&lt;/p&gt;

&lt;p&gt;It contains one entry for every archived file and repeats some information from the local headers. It also includes the offset of each local file record.&lt;/p&gt;

&lt;p&gt;This design allows an extractor to read the directory and quickly find the contents of the archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why data descriptors are useful for streaming
&lt;/h2&gt;

&lt;p&gt;When writing a local file header, a ZIP generator would ideally already know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the final CRC-32;&lt;/li&gt;
&lt;li&gt;the final compressed size;&lt;/li&gt;
&lt;li&gt;the uncompressed size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uncompressed size may be available from the source file.&lt;/p&gt;

&lt;p&gt;The compressed size is not necessarily known until compression has finished.&lt;/p&gt;

&lt;p&gt;The CRC-32 is also calculated while the input is processed.&lt;/p&gt;

&lt;p&gt;A non-streaming implementation can compress the complete file first and then write a fully populated header.&lt;/p&gt;

&lt;p&gt;A streaming implementation cannot always do that without buffering the complete result.&lt;/p&gt;

&lt;p&gt;ZIP provides another mechanism.&lt;/p&gt;

&lt;p&gt;The writer can mark the relevant fields as unavailable in the local header, write the file data, and append a &lt;strong&gt;data descriptor&lt;/strong&gt; containing the final CRC and sizes.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local header
  CRC: not known yet
  compressed size: not known yet
Compressed data is streamed
Data descriptor
  CRC: final value
  compressed size: final value
  original size: final value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the reasons ZIP is suitable for progressive output.&lt;/p&gt;

&lt;p&gt;It was designed with structures that permit archive data to be produced in one pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The central directory is still written at the end
&lt;/h2&gt;

&lt;p&gt;Streaming does not eliminate the need for finalization.&lt;/p&gt;

&lt;p&gt;While files are being written, the ZIP implementation must collect enough metadata to later produce the central directory.&lt;/p&gt;

&lt;p&gt;The archive is not complete until the writer is closed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;close()&lt;/code&gt; writes the remaining directory records and final archive structures.&lt;/p&gt;

&lt;p&gt;This explains why closing and aborting have different meanings.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;close()&lt;/code&gt; means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All entries are valid
→ write the central directory
→ finalize the archive
→ complete the destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;abort()&lt;/code&gt; means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stop producing data
→ discard pending writes where possible
→ do not finalize the archive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partially written ZIP without its correct central directory should not be treated as a successful export.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ZIP64 is necessary
&lt;/h2&gt;

&lt;p&gt;Classic ZIP records contain several 16-bit and 32-bit fields.&lt;/p&gt;

&lt;p&gt;A 32-bit unsigned field can represent values up to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4,294,967,295
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is 4 GiB minus one byte.&lt;/p&gt;

&lt;p&gt;The traditional end-of-central-directory record also uses 16-bit fields for entry counts, creating the well-known limit of 65,535 entries.&lt;/p&gt;

&lt;p&gt;ZIP64 extends the format with larger fields.&lt;/p&gt;

&lt;p&gt;When a classic field cannot contain the real value, it uses a sentinel such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xFFFFFFFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xFFFF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual value is stored in a ZIP64 extra field or ZIP64 end-of-directory structure.&lt;/p&gt;

&lt;p&gt;The PKWARE specification requires ZIP64 records when classic fields are too small for the required values.&lt;/p&gt;

&lt;p&gt;The project displays a simple preliminary indication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ZIP32_MAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;zip64Required&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ZIP32_MAX&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mh"&gt;0xffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for the interface, but it is only an estimate.&lt;/p&gt;

&lt;p&gt;The actual need for ZIP64 depends on values such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the uncompressed size of an individual entry;&lt;/li&gt;
&lt;li&gt;its compressed size;&lt;/li&gt;
&lt;li&gt;the final archive size;&lt;/li&gt;
&lt;li&gt;the offset of each local header;&lt;/li&gt;
&lt;li&gt;the central directory size;&lt;/li&gt;
&lt;li&gt;the number of entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The total input size alone does not fully determine every one of these values.&lt;/p&gt;

&lt;p&gt;For that reason, the final format decision belongs to the ZIP writer.&lt;/p&gt;

&lt;p&gt;The interface offers two modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automatic
Force ZIP64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatic mode is appropriate for normal use.&lt;/p&gt;

&lt;p&gt;Forced mode is useful for testing whether other archive tools correctly support ZIP64 output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding files to the archive
&lt;/h2&gt;

&lt;p&gt;The application processes files sequentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;addEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;completedInputBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strategyFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lastModDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;onprogress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processedBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nx"&gt;completedInputBytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;completedInputBytes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completedFiles&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each browser &lt;code&gt;File&lt;/code&gt; is already a type of &lt;code&gt;Blob&lt;/code&gt;, so it can be passed to zip.js through &lt;code&gt;BlobReader&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The source file does not need to be converted into a complete &lt;code&gt;ArrayBuffer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This avoids an unnecessary application-level copy.&lt;/p&gt;

&lt;p&gt;Files are added one at a time, which also makes the progress model and status interface easier to understand.&lt;/p&gt;

&lt;p&gt;Possible states are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pending
Running
Completed
Cancelled
Error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  DEFLATE and STORE
&lt;/h2&gt;

&lt;p&gt;A ZIP entry does not always need to be compressed.&lt;/p&gt;

&lt;p&gt;The two strategies used by the project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DEFLATE&lt;/strong&gt;, which compresses the input;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STORE&lt;/strong&gt;, which copies the input into the archive without additional compression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For plain text, CSV, JSON, XML, source code, and other repetitive data, DEFLATE can significantly reduce the output size.&lt;/p&gt;

&lt;p&gt;For formats that are already compressed, another DEFLATE pass often produces little or no improvement.&lt;/p&gt;

&lt;p&gt;It may even make the archive slightly larger because of compression metadata.&lt;/p&gt;

&lt;p&gt;It also consumes CPU, increases export time, and may increase power usage on mobile devices.&lt;/p&gt;

&lt;p&gt;The project supports three compression modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automatic
DEFLATE all files
STORE all files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The automatic mode uses an extension and MIME-type heuristic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting already compressed formats
&lt;/h2&gt;

&lt;p&gt;The project defines a set of commonly compressed extensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALREADY_COMPRESSED_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aac&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;avi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;avif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;br&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bz2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;docx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;epub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flac&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m4a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m4v&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mkv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mov&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mp3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ogg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ogv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pptx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xlsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Audio and video MIME prefixes are also treated as already compressed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALREADY_COMPRESSED_MIME_PREFIXES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detection function combines extension and MIME checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;isAlreadyCompressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extensionOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ALREADY_COMPRESSED_EXTENSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ALREADY_COMPRESSED_MIME_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ALREADY_COMPRESSED_MIME_PREFIXES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a heuristic, not a content analysis algorithm.&lt;/p&gt;

&lt;p&gt;A filename can have an incorrect extension.&lt;/p&gt;

&lt;p&gt;A browser may provide an empty or inaccurate MIME type.&lt;/p&gt;

&lt;p&gt;A PDF can contain highly compressible streams, already compressed images, or a mixture of content.&lt;/p&gt;

&lt;p&gt;An Office Open XML document is itself a ZIP-based container, so applying DEFLATE to the complete &lt;code&gt;.docx&lt;/code&gt; or &lt;code&gt;.xlsx&lt;/code&gt; file normally offers limited benefit.&lt;/p&gt;

&lt;p&gt;For a lightweight client-side application, the heuristic is a practical compromise.&lt;/p&gt;

&lt;p&gt;A production application could make this configurable per file or inspect file signatures before choosing the compression method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the compression strategy
&lt;/h2&gt;

&lt;p&gt;The final decision is made by &lt;code&gt;strategyFor()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;strategyFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compressionMode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;STORE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression disabled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compressionMode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEFLATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression forced&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAlreadyCompressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;STORE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;already compressed format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEFLATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;potentially compressible content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A compression level of 6 is used as a reasonable general-purpose balance.&lt;/p&gt;

&lt;p&gt;A higher level can reduce output slightly for some inputs but usually requires more processing time.&lt;/p&gt;

&lt;p&gt;For JPEG and MP4 files, automatic mode selects level 0, which corresponds to STORE.&lt;/p&gt;

&lt;p&gt;The resulting ZIP archive still contains the files normally. They are simply not recompressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progress tracking
&lt;/h2&gt;

&lt;p&gt;Progress reporting for a generated ZIP is less obvious than it initially appears.&lt;/p&gt;

&lt;p&gt;The application knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the size of every source file;&lt;/li&gt;
&lt;li&gt;the total size of all source files;&lt;/li&gt;
&lt;li&gt;how many files have completed;&lt;/li&gt;
&lt;li&gt;the number of input bytes processed for the active file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not necessarily know the final archive size in advance.&lt;/p&gt;

&lt;p&gt;The compression ratio depends on the file contents and selected strategy.&lt;/p&gt;

&lt;p&gt;For this reason, StreamZIP Lab defines global progress in terms of &lt;strong&gt;input bytes processed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The total size is calculated with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;totalBytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ZIP entry callback updates the current file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;onprogress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processedBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;completedInputBytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The percentage is then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;overallPercent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processedBytes&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a stable and understandable progress indicator.&lt;/p&gt;

&lt;p&gt;However, it is important to describe it correctly.&lt;/p&gt;

&lt;p&gt;It means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Percentage of source bytes processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Percentage of final ZIP bytes written
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These metrics are related but not identical.&lt;/p&gt;

&lt;p&gt;For example, a large text file may generate far fewer output bytes than input bytes, while a stored video file may produce almost the same number of input and output bytes.&lt;/p&gt;

&lt;p&gt;The interface also displays:&lt;/p&gt;

&lt;p&gt;the current filename; current-file progress; • •&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;completed file count;&lt;/li&gt;
&lt;li&gt;elapsed time;&lt;/li&gt;
&lt;li&gt;export mode;&lt;/li&gt;
&lt;li&gt;final Blob size when available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In native streaming mode, the application does not calculate the final output size because the archive is written directly to disk and no final Blob is returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancelling the export
&lt;/h2&gt;

&lt;p&gt;Cancellation must affect more than the interface.&lt;/p&gt;

&lt;p&gt;Changing the status label to “Cancelled” while compression continues in the background would not be a real cancellation mechanism.&lt;/p&gt;

&lt;p&gt;The project creates an &lt;code&gt;AbortController&lt;/code&gt; at the start of every export:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its signal is passed to every zip.js entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;onprogress&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user clicks &lt;strong&gt;Cancel export&lt;/strong&gt;, the controller is aborted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;cancelExport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBusy&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cancelRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cancelRequested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aborted&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAbortError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abortNativeWritable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAbortError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application also aborts the native destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;abortNativeWritable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeNativeWritable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;writable&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abort&lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The stream may already be closed or aborted.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This two-level cancellation is useful because it stops both sides of the operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;AbortController&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;stops&lt;/span&gt; &lt;span class="nx"&gt;ZIP&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="nx"&gt;processing&lt;/span&gt;
&lt;span class="nx"&gt;nativeWritable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;stops&lt;/span&gt; &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="nx"&gt;writing&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;cleanup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Streams Standard defines abortion as an error transition for the writable stream and its pending operation.&lt;/p&gt;

&lt;p&gt;After cancellation, the project does not call the normal archive finalization path.&lt;/p&gt;

&lt;p&gt;The destination should therefore not be presented as a successful ZIP archive.&lt;/p&gt;

&lt;p&gt;The exact appearance of an aborted destination can vary by browser and operating system. It may be removed, left empty, or remain as an incomplete file depending on implementation details and when the operation was interrupted.&lt;/p&gt;

&lt;p&gt;The important application rule is that an aborted file must never be reported as a completed export.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling picker cancellation
&lt;/h2&gt;

&lt;p&gt;The user can also cancel the save dialog before the export begins.&lt;/p&gt;

&lt;p&gt;This normally appears as an &lt;code&gt;AbortError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The project distinguishes this expected case from operational failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;isAbortError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sr"&gt;/abort|cancel/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main export method maps it to the cancelled state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAbortError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cancelRequested&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Export cancelled. The destination file was not finalized.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cancelling a picker is not a system failure.&lt;/p&gt;

&lt;p&gt;The user simply decided not to continue.&lt;/p&gt;

&lt;p&gt;Treating it as a normal cancellation produces a better interface than displaying a red error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blob fallback
&lt;/h2&gt;

&lt;p&gt;The application detects native file picker support with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supportsFileSystemAccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;showSaveFilePicker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete support object is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;support: {
  secureContext: window.isSecureContext,
  fileSystemAccess:
    "showSaveFilePicker" in window,
  streams:
    "ReadableStream" in window
    &amp;amp;&amp;amp; "WritableStream" in window
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the native picker is unavailable, the application switches to Blob mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;support&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileSystemAccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exportMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;File System Access API unavailable: Blob fallback enabled.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fallback uses &lt;code&gt;BlobWriter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;exportAsBlob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobWriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BlobWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createZipWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blobWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archiveBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;zipWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;archiveBlob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downloadBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;archiveBlob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The download helper creates a temporary object URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;downloadBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL is not revoked immediately after &lt;code&gt;click()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A short delay gives the browser enough time to begin handling the download before the reference is released.&lt;/p&gt;

&lt;p&gt;The fallback maintains functionality on more browsers, but it does not reproduce the memory characteristics of direct streaming.&lt;/p&gt;

&lt;p&gt;This distinction is explicitly shown in the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions and security
&lt;/h2&gt;

&lt;p&gt;The name “File System Access API” can create the wrong impression.&lt;/p&gt;

&lt;p&gt;A web page does not receive unrestricted access to the user’s computer.&lt;/p&gt;

&lt;p&gt;For the picker-based workflow:&lt;/p&gt;

&lt;p&gt;The page requests a picker. 1.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser displays a user-controlled dialog.&lt;/li&gt;
&lt;li&gt;The user selects a file destination.&lt;/li&gt;
&lt;li&gt;The page receives a handle for that selected file.&lt;/li&gt;
&lt;li&gt;The page can write through the granted handle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Access is explicitly gated by the picker. The specification also recommends that browsers restrict sensitive files and directories, including system locations and browser data directories.&lt;/p&gt;

&lt;p&gt;The project never receives a traditional absolute path such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\Name\Documents\archive.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/home/name/Documents/archive.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser returns an opaque handle.&lt;/p&gt;

&lt;p&gt;This separation prevents the application from depending on operating-system-specific path structures and limits what the site can access.&lt;/p&gt;

&lt;p&gt;The project also normalizes suggested archive names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;normalizeArchiveName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamzip-export.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\\/&lt;/span&gt;&lt;span class="sr"&gt;:*?"&amp;lt;&amp;gt;|&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;0000-&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;001f&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt; &lt;span class="nx"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamzip-export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.zip`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a replacement for browser security.&lt;/p&gt;

&lt;p&gt;It is an application-level measure that avoids suggesting names containing characters commonly invalid on desktop operating systems.&lt;/p&gt;

&lt;p&gt;The browser still has the final authority over accepted names and destinations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure contexts
&lt;/h2&gt;

&lt;p&gt;Powerful browser capabilities generally require a secure context.&lt;/p&gt;

&lt;p&gt;The project checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.isSecureContext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For development, the recommended setup is a local HTTP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then be opened at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Localhost is treated as a trustworthy development context by modern browsers.&lt;/p&gt;

&lt;p&gt;For production, the application should be served over HTTPS.&lt;/p&gt;

&lt;p&gt;Opening the HTML file directly from the filesystem is not the best testing method because browser behavior, module loading, security policies, and file API support may differ from a normal hosted environment.&lt;/p&gt;

&lt;p&gt;A local server also more closely represents how the project will behave after deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application state
&lt;/h2&gt;

&lt;p&gt;The project uses an explicit state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idle
preparing
running
success
cancelled
error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The states are reflected in both the interface and the export logic.&lt;/p&gt;

&lt;p&gt;At the beginning of a run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preparing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the destination is authorized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the archive is finalized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An expected interruption produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unexpected exception produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation prevents several interface problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starting two exports at the same time;&lt;/li&gt;
&lt;li&gt;removing files during compression;&lt;/li&gt;
&lt;li&gt;enabling the export button without files;&lt;/li&gt;
&lt;li&gt;showing success after cancellation;&lt;/li&gt;
&lt;li&gt;leaving a file marked as running after an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The busy state is computed from the current status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;isBusy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preparing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buttons and inputs bind their disabled state to this value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplicate and unsafe filenames
&lt;/h2&gt;

&lt;p&gt;Multiple selected files can have the same name.&lt;/p&gt;

&lt;p&gt;This is common when files are selected from different directories.&lt;/p&gt;

&lt;p&gt;A ZIP archive can technically contain duplicate names, but the extraction behavior can be confusing. Some tools overwrite one entry, while others show both.&lt;/p&gt;

&lt;p&gt;StreamZIP Lab generates unique archive names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;photo.jpg
photo (2).jpg
photo (3).jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic separates the base name from the extension and increments a counter until an unused name is found.&lt;/p&gt;

&lt;p&gt;It also replaces characters that commonly create cross-platform filename problems.&lt;/p&gt;

&lt;p&gt;This does not preserve the original directory hierarchy because the project receives a flat file selection.&lt;/p&gt;

&lt;p&gt;A future version could use directory selection and store relative paths inside the archive.&lt;/p&gt;

&lt;p&gt;That extension would require additional care to prevent unsafe archive paths such as &lt;code&gt;../&lt;/code&gt;, absolute paths, or platform-specific traversal patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical logging
&lt;/h2&gt;

&lt;p&gt;The application includes a small log panel.&lt;/p&gt;

&lt;p&gt;Typical messages are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Added 4 file(s) (1.8 GB).
Starting export in Stream to disk.
Destination authorized: streamzip-export.zip.
document.txt: DEFLATE (potentially compressible content).
video.mp4: STORE (already compressed format).
Archive completed successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logging is particularly useful for browser APIs because the user may otherwise see only a generic failure.&lt;/p&gt;

&lt;p&gt;The log helps distinguish between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDN loading errors;&lt;/li&gt;
&lt;li&gt;picker cancellation;&lt;/li&gt;
&lt;li&gt;missing secure context;&lt;/li&gt;
&lt;li&gt;unsupported streams;&lt;/li&gt;
&lt;li&gt;compression failure;&lt;/li&gt;
&lt;li&gt;destination write failure;&lt;/li&gt;
&lt;li&gt;explicit cancellation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application limits the log to 80 entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents an indefinitely growing reactive array during repeated tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three project files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  index.html
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt; contains the Vue template and external scripts.&lt;/p&gt;

&lt;p&gt;The interface is divided into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser support;&lt;/li&gt;
&lt;li&gt;file selection;&lt;/li&gt;
&lt;li&gt;export configuration;&lt;/li&gt;
&lt;li&gt;progress;&lt;/li&gt;
&lt;li&gt;technical log;&lt;/li&gt;
&lt;li&gt;educational explanations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the global Vue build compiles templates in the browser, directives can be written directly in the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button button-primary"&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"exportArchive"&lt;/span&gt;&lt;span class="na"&gt;:disabled=&lt;/span&gt;&lt;span class="s"&gt;"!canExport"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Export ZIP archive
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reactive values are displayed with normal Vue interpolation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;{{ overallPercent }}%&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  styles.css
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;styles.css&lt;/code&gt; implements the complete visual layer.&lt;/p&gt;

&lt;p&gt;It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the responsive page layout;&lt;/li&gt;
&lt;li&gt;drag-and-drop states;&lt;/li&gt;
&lt;li&gt;form controls;&lt;/li&gt;
&lt;li&gt;file status chips;&lt;/li&gt;
&lt;li&gt;progress bars;&lt;/li&gt;
&lt;li&gt;warning panels;&lt;/li&gt;
&lt;li&gt;technical metrics;&lt;/li&gt;
&lt;li&gt;archive structure diagrams;&lt;/li&gt;
&lt;li&gt;accessible hidden labels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CSS is deliberately independent from a component framework.&lt;/p&gt;

&lt;p&gt;This keeps the project free from another runtime dependency and makes it possible to study the application state without utility-class noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  functions.js
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;functions.js&lt;/code&gt; contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue initialization;&lt;/li&gt;
&lt;li&gt;file management;&lt;/li&gt;
&lt;li&gt;filename normalization;&lt;/li&gt;
&lt;li&gt;format detection;&lt;/li&gt;
&lt;li&gt;compression selection;&lt;/li&gt;
&lt;li&gt;picker integration;&lt;/li&gt;
&lt;li&gt;stream adaptation;&lt;/li&gt;
&lt;li&gt;ZIP generation;&lt;/li&gt;
&lt;li&gt;progress calculation;&lt;/li&gt;
&lt;li&gt;cancellation;&lt;/li&gt;
&lt;li&gt;Blob downloads;&lt;/li&gt;
&lt;li&gt;error handling;&lt;/li&gt;
&lt;li&gt;logging;&lt;/li&gt;
&lt;li&gt;size formatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire JavaScript file is wrapped in an immediately invoked function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Application code&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents project constants and helper functions from being added directly to the global scope.&lt;/p&gt;

&lt;p&gt;Vue and zip.js remain global because they are loaded through classic script tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the project
&lt;/h2&gt;

&lt;p&gt;A useful test plan should include several different file types and sizes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: compressible text
&lt;/h3&gt;

&lt;p&gt;Select a large &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.csv&lt;/code&gt;, or &lt;code&gt;.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Expected result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy: DEFLATE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final archive should be significantly smaller if the input contains repeated data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: JPEG image
&lt;/h3&gt;

&lt;p&gt;Select a &lt;code&gt;.jpg&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Expected result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy: STORE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ZIP output should be only slightly larger than the image because the ZIP still needs headers and directory records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 3: MP4 video
&lt;/h3&gt;

&lt;p&gt;Select a large &lt;code&gt;.mp4&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Expected result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy: STORE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a good test for progressive writing because the input may be large while additional compression is unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 4: forced compression
&lt;/h3&gt;

&lt;p&gt;Choose &lt;strong&gt;DEFLATE all files&lt;/strong&gt; and export the same JPEG or MP4.&lt;/p&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;elapsed time;&lt;/li&gt;
&lt;li&gt;output size;&lt;/li&gt;
&lt;li&gt;CPU usage;&lt;/li&gt;
&lt;li&gt;responsiveness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many cases, the additional work produces very little size reduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 5: Blob mode
&lt;/h3&gt;

&lt;p&gt;Export the same set of files through Blob mode.&lt;/p&gt;

&lt;p&gt;Observe that the browser download starts only after the complete archive has been created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 6: cancellation
&lt;/h3&gt;

&lt;p&gt;Begin exporting a large file and press &lt;strong&gt;Cancel export&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The interface should enter the cancelled state and the archive should not be reported as complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 7: unsupported API
&lt;/h3&gt;

&lt;p&gt;Run the application in a browser without &lt;code&gt;showSaveFilePicker()&lt;/code&gt; support.&lt;/p&gt;

&lt;p&gt;The destination option should automatically fall back to Blob mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 8: duplicate names
&lt;/h3&gt;

&lt;p&gt;Select several files with identical names from different directories.&lt;/p&gt;

&lt;p&gt;The application should assign unique ZIP entry names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 9: ZIP64
&lt;/h3&gt;

&lt;p&gt;Force ZIP64 and verify the result with an archive utility that exposes format details.&lt;/p&gt;

&lt;p&gt;For a real automatic ZIP64 test, the project needs either a very large entry, a very large archive, or more than 65,535 entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of the current implementation
&lt;/h2&gt;

&lt;p&gt;StreamZIP Lab is intentionally small, so it has several limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser support
&lt;/h3&gt;

&lt;p&gt;The native picker is not available in every browser.&lt;/p&gt;

&lt;p&gt;Feature detection is mandatory, and Blob fallback remains necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  CDN dependency
&lt;/h3&gt;

&lt;p&gt;Vue and zip.js are loaded from a CDN.&lt;/p&gt;

&lt;p&gt;The application therefore requires network access on its first load unless those files are cached externally.&lt;/p&gt;

&lt;p&gt;A real offline PWA should self-host or precache pinned library versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  No directory hierarchy
&lt;/h3&gt;

&lt;p&gt;The project accepts a flat list of files.&lt;/p&gt;

&lt;p&gt;It does not currently reproduce directory structures inside the ZIP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Progress is based on input bytes
&lt;/h3&gt;

&lt;p&gt;The displayed percentage represents source processing, not exact destination bytes.&lt;/p&gt;

&lt;h3&gt;
  
  
  No persistent handles
&lt;/h3&gt;

&lt;p&gt;The file handle is used only for the current operation.&lt;/p&gt;

&lt;p&gt;The project does not store it in IndexedDB for later reuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  No encryption
&lt;/h3&gt;

&lt;p&gt;The generated archive is not password-protected or encrypted.&lt;/p&gt;

&lt;h3&gt;
  
  
  No integrity verification after writing
&lt;/h3&gt;

&lt;p&gt;The project trusts successful completion of the ZIP writer and destination stream.&lt;/p&gt;

&lt;p&gt;It does not reopen the final archive and verify every entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main interface and compression workload
&lt;/h3&gt;

&lt;p&gt;zip.js can use workers, but very large or complex workloads still require testing on low-end devices.&lt;/p&gt;

&lt;p&gt;Production applications should measure responsiveness under realistic conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements for a production PWA
&lt;/h2&gt;

&lt;p&gt;The project could be extended in several directions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a manifest and service worker
&lt;/h3&gt;

&lt;p&gt;This would turn the browser lab into an installable PWA.&lt;/p&gt;

&lt;p&gt;The service worker could cache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;styles.css&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;functions.js&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Vue;&lt;/li&gt;
&lt;li&gt;zip.js;&lt;/li&gt;
&lt;li&gt;icons and manifest assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Self-host dependencies
&lt;/h3&gt;

&lt;p&gt;Pinned local library files would reduce CDN dependency and simplify a strict Content Security Policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add directory selection
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;showDirectoryPicker()&lt;/code&gt; could be used to recursively collect files and preserve their relative paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persist authorized handles
&lt;/h3&gt;

&lt;p&gt;File and directory handles can be stored for workflows where the user returns to the same export destination.&lt;/p&gt;

&lt;p&gt;Permission state must still be checked when the application starts again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add per-file compression controls
&lt;/h3&gt;

&lt;p&gt;The user could override automatic decisions for individual files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a worker-based orchestration layer
&lt;/h3&gt;

&lt;p&gt;A dedicated worker could coordinate preprocessing, checksums, metadata generation, or transformations without blocking interface updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add output verification
&lt;/h3&gt;

&lt;p&gt;After writing, the application could reopen the archive, read its central directory, and verify expected names, sizes, and CRC values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add reproducible benchmarks
&lt;/h3&gt;

&lt;p&gt;A benchmark mode could compare streaming and Blob output using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identical inputs;&lt;/li&gt;
&lt;li&gt;compression level;&lt;/li&gt;
&lt;li&gt;elapsed time;&lt;/li&gt;
&lt;li&gt;JavaScript heap measurements where available;&lt;/li&gt;
&lt;li&gt;output size;&lt;/li&gt;
&lt;li&gt;browser version;&lt;/li&gt;
&lt;li&gt;device information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current risk indicator is educational. It is not a substitute for controlled performance measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;StreamZIP Lab started as a small experiment with &lt;code&gt;showSaveFilePicker()&lt;/code&gt;, but building the export workflow required considering several connected topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser memory behavior;&lt;/li&gt;
&lt;li&gt;stream backpressure;&lt;/li&gt;
&lt;li&gt;user activation;&lt;/li&gt;
&lt;li&gt;filesystem permissions;&lt;/li&gt;
&lt;li&gt;ZIP record structure;&lt;/li&gt;
&lt;li&gt;ZIP64 limits;&lt;/li&gt;
&lt;li&gt;compression strategy;&lt;/li&gt;
&lt;li&gt;progress semantics;&lt;/li&gt;
&lt;li&gt;cancellation;&lt;/li&gt;
&lt;li&gt;compatibility fallbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important architectural difference is not simply replacing one API call with another.&lt;/p&gt;

&lt;p&gt;Blob output and streaming output represent two different lifecycle models.&lt;/p&gt;

&lt;p&gt;With a Blob, the application normally completes the artifact first and delivers it afterward.&lt;/p&gt;

&lt;p&gt;With streaming, generation and persistence happen at the same time.&lt;/p&gt;

&lt;p&gt;For large browser-generated files, this difference can determine whether the export is practical at all.&lt;/p&gt;

&lt;p&gt;The File System Access API makes browser applications feel closer to desktop software, but it keeps the user in control through explicit file pickers and permission boundaries.&lt;/p&gt;

&lt;p&gt;Combined with Web Streams and a ZIP implementation that supports progressive output, it becomes possible to create large archives without intentionally materializing the complete result as one application-managed Blob.&lt;/p&gt;

&lt;p&gt;The Blob fallback is still valuable for compatibility.&lt;/p&gt;

&lt;p&gt;The main design principle is to treat it as a fallback with different memory characteristics, not as an equivalent implementation.&lt;/p&gt;

&lt;p&gt;The complete source code is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sfestacatenate/StreamZipLab_Vue_Javascript" rel="noopener noreferrer"&gt;https://github.com/sfestacatenate/StreamZipLab_Vue_Javascript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Linear Regression PWA with React</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Thu, 23 Jul 2026 15:46:46 +0000</pubDate>
      <link>https://dev.to/certosinolab/building-a-linear-regression-pwa-with-react-2mkk</link>
      <guid>https://dev.to/certosinolab/building-a-linear-regression-pwa-with-react-2mkk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ukauo98rhnhy9w4c42a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ukauo98rhnhy9w4c42a.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From a CSV file to an interactive chart, entirely in the browser
&lt;/h2&gt;

&lt;p&gt;Linear regression is one of the simplest statistical techniques, but it is also a useful example of how data processing, mathematics and visualization can be combined in a modern web application.&lt;/p&gt;

&lt;p&gt;In this project, I built a Progressive Web App with React that allows users to upload a CSV file containing pairs of X and Y values. The application processes the data, calculates a linear regression model, displays the resulting equation and plots both the original observations and the regression line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbg3pqh52bju3dvoly9pq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbg3pqh52bju3dvoly9pq.webp" alt=" " width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The technology stack
&lt;/h2&gt;

&lt;p&gt;The application was created with React and Vite. A few focused libraries handle the main features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Papa Parse reads and processes the uploaded CSV file.&lt;/li&gt;
&lt;li&gt;Chart.js and react-chartjs-2 display the dataset and regression line.&lt;/li&gt;
&lt;li&gt;KaTeX renders the regression equation in mathematical notation.&lt;/li&gt;
&lt;li&gt;vite-plugin-pwa adds the web app manifest, service worker and installable PWA functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs directly in the browser. The CSV file does not need to be uploaded to a server, which keeps the application simple and preserves the privacy of the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing the CSV data
&lt;/h2&gt;

&lt;p&gt;When the user selects a file, Papa Parse reads its rows. The application treats the first two values of each row as the X and Y coordinates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invalid or non-numeric rows are filtered out. Once the data has been cleaned, it is stored in the React state and passed to the regression function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating the regression line
&lt;/h2&gt;

&lt;p&gt;The application implements ordinary least squares directly in JavaScript. It calculates the sums of X, Y, XY and X², and then uses them to obtain the slope and intercept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;slope = (nΣxy − ΣxΣy) / (nΣx² − (Σx)²)

intercept = (Σy − slopeΣx) / n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final model has the familiar form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y = slope × x + intercept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code also determines the minimum and maximum X values in the dataset. These values are used to generate the two endpoints of the regression line, avoiding the need to calculate a separate fitted point for every observation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing the result
&lt;/h2&gt;

&lt;p&gt;The graph is rendered through &lt;code&gt;react-chartjs-2&lt;/code&gt;. One dataset represents the original observations as individual points, while a second dataset represents the fitted regression line.&lt;/p&gt;

&lt;p&gt;Although the React &lt;code&gt;Line&lt;/code&gt; component is used, the original dataset has &lt;code&gt;showLine&lt;/code&gt; set to &lt;code&gt;false&lt;/code&gt;. This produces a scatter-style visualization, while the regression dataset remains a continuous line.&lt;/p&gt;

&lt;p&gt;The application also displays the minimum and maximum values of both variables and renders the regression equation with KaTeX. A small conversion function attempts to represent decimal coefficients as fractions, making the output easier to read in some datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making predictions
&lt;/h2&gt;

&lt;p&gt;After calculating the model, users can enter a new X value. The application applies the previously calculated slope and intercept and returns the expected Y value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slope&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns the project from a static visualization into a simple interactive prediction tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning it into a PWA
&lt;/h2&gt;

&lt;p&gt;The PWA configuration is handled through &lt;code&gt;vite-plugin-pwa&lt;/code&gt;. The project defines an application manifest, installable icons, a standalone display mode and an automatic service-worker update strategy.&lt;/p&gt;

&lt;p&gt;Static assets such as JavaScript, CSS, HTML and images are cached through Workbox. As a result, the application can be installed on supported devices and continue to work even when the network connection is unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;This project demonstrates that a useful data-analysis application does not always require a backend or a large machine-learning framework.&lt;/p&gt;

&lt;p&gt;With React, a CSV parser and a charting library, it is possible to create a lightweight tool that imports data, performs a real statistical calculation, visualizes the results and works as an installable application.&lt;/p&gt;

&lt;p&gt;The complete source code is available in the project’s public GitHub repository:&lt;br&gt;
&lt;a href="https://github.com/sfestacatenate/React_PWA_Linear_Regression" rel="noopener noreferrer"&gt;https://github.com/sfestacatenate/React_PWA_Linear_Regression&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Link to the project:&lt;br&gt;
&lt;a href="https://pwa-linear-regression-react.surge.sh" rel="noopener noreferrer"&gt;https://pwa-linear-regression-react.surge.sh&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>react</category>
      <category>javascript</category>
      <category>vite</category>
    </item>
    <item>
      <title>Exploring Database-First Approach with Entity Framework in .NET Core 6</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Fri, 16 Jun 2023 09:52:19 +0000</pubDate>
      <link>https://dev.to/certosinolab/exploring-database-first-approach-with-entity-framework-in-net-core-6-4ibe</link>
      <guid>https://dev.to/certosinolab/exploring-database-first-approach-with-entity-framework-in-net-core-6-4ibe</guid>
      <description>&lt;h2&gt;
  
  
  Exploring Database-First Approach with Entity Framework in .NET Core 6
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Introduction:&lt;/strong&gt; In the world of software development, the efficient management of databases is crucial for the success of any application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dotnet Core 6&lt;/strong&gt; provides powerful tools and features for working with databases. One popular approach is the &lt;strong&gt;database-first approach&lt;/strong&gt;, which allows developers to design their database schema first and then generate the corresponding models and context using the Entity Framework. In this article, we will explore how to utilize the Entity Framework in a database-first manner using .NET Core 6.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Generating the Database Script for Microsoft SQL Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To demonstrate the database-first approach, let’s consider the example of an ecommerce application. The database for this application consists of the following tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Products&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Orders&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order_Details&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Product_Categories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Categories&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Create Products table
    CREATE TABLE Products (
        ProductId INT PRIMARY KEY,
        Name NVARCHAR(100) NOT NULL,
        Price DECIMAL(10, 2) NOT NULL,
        Description NVARCHAR(MAX)
    );

    -- Create Customers table
    CREATE TABLE Customers (
        CustomerId INT PRIMARY KEY,
        FirstName NVARCHAR(50) NOT NULL,
        LastName NVARCHAR(50) NOT NULL,
        Email NVARCHAR(100) NOT NULL
    );

    -- Create Orders table
    CREATE TABLE Orders (
        OrderId INT PRIMARY KEY,
        CustomerId INT NOT NULL,
        OrderDate DATETIME NOT NULL,
        TotalAmount DECIMAL(10, 2) NOT NULL,
        FOREIGN KEY (CustomerId) REFERENCES Customers(CustomerId)
    );

    -- Create Order_Details table
    CREATE TABLE Order_Details (
        OrderId INT NOT NULL,
        ProductId INT NOT NULL,
        Quantity INT NOT NULL,
        Price DECIMAL(10, 2) NOT NULL,
        PRIMARY KEY (OrderId, ProductId),
        FOREIGN KEY (OrderId) REFERENCES Orders(OrderId),
        FOREIGN KEY (ProductId) REFERENCES Products(ProductId)
    );

    -- Create Categories table
    CREATE TABLE Categories (
        CategoryId INT PRIMARY KEY,
        Name NVARCHAR(50) NOT NULL
    );

    -- Create Product_Categories table
    CREATE TABLE Product_Categories (
        ProductId INT NOT NULL,
        CategoryId INT NOT NULL,
        PRIMARY KEY (ProductId, CategoryId),
        FOREIGN KEY (ProductId) REFERENCES Products(ProductId),
        FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId)
    );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create a new Visual Studio Project and install necessary packages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Visual Studio Community 2022&lt;/strong&gt; create for example a Console Application, select .NET 6.0. Now install &lt;strong&gt;Entity Framework Packages&lt;/strong&gt;, we need to install several packages using &lt;strong&gt;NuGet&lt;/strong&gt;. Follow these steps to install the required packages in Visual Studio Community 2022:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open your project in Visual Studio Community 2022.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Right-click on the project in the Solution Explorer and select “Manage NuGet Packages.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the NuGet Package Manager window, search for the following packages one by one:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.Design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.SqlServer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft.EntityFrameworkCore.Tools&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Select each package and click on the “Install” button to install them into your project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Generating Models and Context Using Scaffold-DbContext&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the necessary packages are installed, we can generate the models and the database context using the &lt;strong&gt;Scaffold-DbContext command&lt;/strong&gt;. This command helps in automatically scaffolding the entity types for the existing database schema.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the Models directory, you can choose any name of course&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the Package Manager Console by navigating to “Tools” -&amp;gt; “NuGet Package Manager” -&amp;gt; “Package Manager Console.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the Package Manager Console, run the following command:&lt;/p&gt;

&lt;p&gt;PM&amp;gt; Scaffold-DbContext "Data Source=localhost\SQLEXPRESS;Initial Catalog=ecommerce;Integrated Security=True;TrustServerCertificate=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Scaffold-DbContext command will examine the database schema and generate the corresponding model classes and the database context. These generated classes will be placed in the specified output directory (Models in this case).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybrnbrd2pdcxhgmwlhyo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybrnbrd2pdcxhgmwlhyo.jpg" width="451" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By following the above steps, we have explored the database-first approach using Entity Framework in .NET Core 6. We started by creating a database script for an ecommerce application and defined the necessary relationships. Then, we installed the required Entity Framework packages using NuGet in Visual Studio Community 2022. Finally, we utilized the Scaffold-DbContext command to automatically generate the model classes and the database context based on the existing database schema.&lt;/p&gt;

&lt;p&gt;Utilizing the Entity Framework in a database-first manner allows developers to seamlessly integrate their existing databases with their .NET Core applications. This approach simplifies the development process by eliminating the need for manual creation of models and context classes, saving time and effort. With .NET Core 6 and Entity Framework, developers can build robust and scalable applications with ease.&lt;/p&gt;

&lt;p&gt;Thank you!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>sql</category>
      <category>dotnetcore</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Basic Text Analysis with Python and MongoDB</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Sun, 07 May 2023 23:50:41 +0000</pubDate>
      <link>https://dev.to/certosinolab/basic-text-analysis-with-python-and-mongodb-26n2</link>
      <guid>https://dev.to/certosinolab/basic-text-analysis-with-python-and-mongodb-26n2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Have you ever needed to analyze unstructured text data? Python can help you do just that. In this article, we’ll look at a basic example of parsing unstructured text data located in MongoDB using Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Write the Code
&lt;/h2&gt;

&lt;p&gt;First, let’s connect to a MongoDB client and retrieve all the documents from a collection in our database:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pymongo
import re
from matplotlib import pyplot as plt

# Connect to a MongoDB client
client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["your_db"]
col = db["your_collection"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we’ll create an empty string that will contain all the offer details from each document:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create an empty string that contains all texts
all_details_string = ''

# Iterate over all documents in your MongoDB instance
for doc in col.find():
    all_details_string = all_details_string + doc.get('offer_details').upper()

# Create a list containing all words
doc_general = re.split(" |/|\n", all_details_string)

# Get all words count
all_words_count = len(doc_general)
print('Total Occurrences:', all_words_count)

# Set technologies to analise
tec_list = ['Java', 'C#', 'Angular', 'React']
count_list = []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On the list of the technologies we want to analyze, iterate through each one, counting the number of occurrences in the concatenated string and adding the count to our list:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for tec in tec_list:
    count_list.append(doc_general.count(tec.upper()))
    print(tec, 'Total Occurrences:', doc_general.count(tec.upper()))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In my environment, i got the following result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftvr6cqw63m66xnsrnqj4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftvr6cqw63m66xnsrnqj4.jpeg" width="252" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we’ll create a bar chart using the Matplotlib library, with the technologies on the x-axis and their respective counts on the y-axis:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a bar chart
labels = tec_list
values = count_list
fig, ax = plt.subplots()
ax.bar(labels, values)

# Add labels and title
ax.set_ylabel('Occurrence')
ax.set_xlabel('Technology')
ax.set_title('Occurrences of the chosen technologies')

# Show the chart
plt.show()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhp8cfoj56es7qilb8fs.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhp8cfoj56es7qilb8fs.jpeg" width="546" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete code and final thoughts
&lt;/h2&gt;

&lt;p&gt;By analyzing unstructured text data, we can gain insights into the most common words and topics. This can be useful for a wide range of applications, such as sentiment analysis, topic modeling, and more.&lt;/p&gt;

&lt;p&gt;Here the full code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pymongo
import re
from matplotlib import pyplot as plt

# Connect to a MongoDB client
client = pymongo.MongoClient(“mongodb://localhost:27017/”)
db = client[“your_db”]
col = db[“your_collection”]

# Create an empty string that contains all texts
all_details_string = ‘’
list_detail_offer = []

# Iterate over all documents in your MongoDB instance
for doc in col.find():
 all_details_string = all_details_string + doc.get(‘offer_details’).upper()

# Create a list containing all words
doc_general = re.split(“ |/|\n”, all_details_string)

# Get all words count
all_words_count = len(doc_general)
print(‘Total Occurrences:’, all_words_count)

# Set technologies to analise
tec_list = [‘Java’, ‘C#’, ‘Angular’, ‘React’]
count_list = []
for tec in tec_list:
 count_list.append(doc_general.count(tec.upper()))
 print(tec, ‘Total Occurrences:’, doc_general.count(tec.upper()))

# Create a bar chart
labels = tec_list
values = count_list
fig, ax = plt.subplots()
ax.bar(labels, values)

# Add labels and title
ax.set_ylabel(‘Occurrence’)
ax.set_xlabel(‘Technology’)
ax.set_title(‘Occurrences of the chosen technologies’)

# Show the chart
plt.show()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Thank you!&lt;/p&gt;

</description>
      <category>python</category>
      <category>mongodb</category>
      <category>analysis</category>
      <category>matplotlib</category>
    </item>
    <item>
      <title>Find occurrences of a word in a Pdf file with c# and PdfPig</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Sun, 07 May 2023 17:11:59 +0000</pubDate>
      <link>https://dev.to/certosinolab/find-occurrences-of-a-word-in-a-pdf-file-with-c-and-pdfpig-4l0b</link>
      <guid>https://dev.to/certosinolab/find-occurrences-of-a-word-in-a-pdf-file-with-c-and-pdfpig-4l0b</guid>
      <description>&lt;h2&gt;
  
  
  Introducing PdfPig
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PdfPig&lt;/strong&gt; is an &lt;strong&gt;open source C # library&lt;/strong&gt; that allows us to &lt;strong&gt;extract text&lt;/strong&gt; and other content from &lt;strong&gt;pdfs&lt;/strong&gt;. Its a port of the java pdfbox library. You can find more here: &lt;a href="https://github.com/UglyToad/PdfPig" rel="noopener noreferrer"&gt;https://github.com/UglyToad/PdfPig&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Word Counter
&lt;/h2&gt;

&lt;p&gt;The project will be a simple console application and will have the following structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feyvn79dcfxbtewzrtpi4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feyvn79dcfxbtewzrtpi4.png" width="369" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating The Project
&lt;/h2&gt;

&lt;p&gt;With Visual Studio 2022, follow the steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open Visual Studio 2022&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create New Project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Console Application in C#&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set Name and Path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose .NET 5.0 Framework&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now you have the basic structure of a console application. Create a folder and call it pdf. Add a pdf inside this folder. In this tutorial i used a pdf created from this page: &lt;a href="https://en.wikipedia.org/wiki/Cr%C3%AApe" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Cr%C3%AApe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get PdfPig:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;On the search bar print Manage NuGet Packages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on Browse&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search PdfPig&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install It&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Thanks to PdfPig extracting text from the pdf and calculating the occurrences of a word is trivial, here the full code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System;
using UglyToad.PdfPig;
using UglyToad.PdfPig.Content;

namespace pdf_pig_word_counter
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string wordToFind = "pancake";
            int numberOfOccurrences = 0;

using (PdfDocument document = PdfDocument.Open(@"YOUR PATH\pdf\test.pdf"))
            {
                foreach (Page page in document.GetPages())
                {
                    string pageText = page.Text;

foreach (Word word in page.GetWords())
                    {
                        if (word.Text.ToLower().Contains(wordToFind.ToLower()))
                            numberOfOccurrences++;
                    }
                }
                Console.WriteLine("Total Occurrences: " + numberOfOccurrences);
            }
        }

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

&lt;/div&gt;

&lt;p&gt;This program will tell us how many times the word pancake is present in the pdf.&lt;/p&gt;

&lt;p&gt;You can find the project here: &lt;a href="https://github.com/CertosinoLab/mediumarticles/tree/pdf_pig_word_counter" rel="noopener noreferrer"&gt;https://github.com/CertosinoLab/mediumarticles/tree/pdf_pig_word_counter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>pdf</category>
      <category>backend</category>
    </item>
    <item>
      <title>Using Event Bus in Vue.js 3</title>
      <dc:creator>CertosinoLab</dc:creator>
      <pubDate>Sun, 07 May 2023 16:02:31 +0000</pubDate>
      <link>https://dev.to/certosinolab/using-event-bus-in-vuejs-3-1lg6</link>
      <guid>https://dev.to/certosinolab/using-event-bus-in-vuejs-3-1lg6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In vue js we have essentially three ways of making unrelated components communicate with each other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vuex&lt;/li&gt;
&lt;li&gt;Pinia&lt;/li&gt;
&lt;li&gt;Event Bus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;a href="https://v3.vuejs.org/guide/migration/events-api.html#event-bus" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; we can read:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In most circumstances, using a global event bus for communicating between components is discouraged. While it is often the simplest solution in the short term, it almost invariably proves to be a maintenance headache in the long term.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So generally in the long run it is better to use vuex.&lt;/p&gt;

&lt;p&gt;Also in the &lt;a href="https://v3.vuejs.org/guide/migration/events-api.html#_2-x-syntax" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; you can see how the Event Bus implementation has changed from Vue 2 to Vue 3&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffolding the project
&lt;/h2&gt;

&lt;p&gt;In this tutorial we will use Vite build tool to scaffold the project&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# npm 6.x
npm init vite@latest vue-event-bus-1 --template vue

# npm 7+, extra double-dash is needed:
npm init vite@latest vue-event-bus-1 -- --template vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;then we need to install an external library implementing the event emitter interface, in this case &lt;a href="https://github.com/developit/mitt" rel="noopener noreferrer"&gt;mitt&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save mitt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;The project consists of two simple components, &lt;strong&gt;FirstComponent.vue&lt;/strong&gt; and &lt;strong&gt;SecondComponent.vue&lt;/strong&gt;, when we click on the emit event button in &lt;strong&gt;SecondComponent.vue&lt;/strong&gt; String to change in **FirstComponent.vue **becomes String changed thanks to the Event Bus&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4x3gc7azp96llstisf65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4x3gc7azp96llstisf65.png" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;In **main.js **we create an instance of mitt which is used globally&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ***createApp ***} from 'vue'
import mitt from 'mitt'
import App from './App.vue'


const emitter = mitt()
const app = ***createApp***(App)

app.config.globalProperties.emitter = emitter
app.mount('#app')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can access emitter globally, so in &lt;strong&gt;FirstComponent.vue&lt;/strong&gt; we subscribe to the event emitted by &lt;strong&gt;SecondComponent.vue.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look to &lt;strong&gt;FirstComponent.vue&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
export default {
  data: function () {
    return {
      testEvent: 'String to change'
    }
  },
  created (){
    this.emitter.on('my-event', (evt) =&amp;gt; {
      this.testEvent = evt.eventContent;
    })
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now in **SecondComponent.vue **let’s emit the event&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;br&gt;
    &amp;lt;div style="border: 1px solid black;"&amp;gt;&lt;br&gt;
        &amp;lt;h1&amp;gt;Second Component&amp;lt;/h1&amp;gt;&lt;br&gt;
        &amp;lt;button v-on:click="emitMyEvent"&amp;gt;Emit Event&amp;lt;/button&amp;gt;&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
&amp;lt;/template&amp;gt;

&lt;p&gt;&amp;lt;script&amp;gt;&lt;br&gt;
    export default {&lt;br&gt;
        methods: {&lt;br&gt;
          emitMyEvent() {&lt;br&gt;
              this.emitter.emit('my-event', {'eventContent': 'String changed'})&lt;br&gt;
          }&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
&amp;lt;/script&amp;gt;&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Github&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;You can find the code of this tutorial on github: &lt;a href="https://github.com/CertosinoLab/mediumarticles/tree/vue-event-bus-1" rel="noopener noreferrer"&gt;CertosinoLab/mediumarticles at vue-event-bus-1 (github.com)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>vite</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
