Browser builds

An HTML entry lets the browser start at a document rather than a module:

JSON
{ "targets": { "web": { "entry": "index.html", "outdir": "dist" } } }

The tags in it are the build's inputs:

HTML
<link rel="stylesheet" href="./styles.css" />
<script type="module" src="./src/entry.client.tsx"></script>

They become hashed URLs in dist/index.html:

HTML
<link rel="stylesheet" href="/assets/styles-621d3b66.css" />
<script type="module" src="/assets/entry.client-fccaa347.js"></script>
<script type="module">An entry — it and everything it imports become one browser bundle
Everything else relativeCopied: stylesheets, favicons, images, classic scripts
BothContent-hashed into <outdir>/assets, so the whole directory caches immutably
Everything else in the fileUntouched byte for byte — title, meta, Open Graph and inline scripts

A relative path is an input. A rooted path, a URL and a data: URI are left exactly as written — the escape hatch for anything the build should keep out of. Dynamic import() chunks land in the same directory, hashed by the bundler.

Imported assets

TypeScript
import logo from "./logo.png";
document.body.innerHTML = `<img src="${logo}">`;

The file is emitted with a content hash and the import becomes its URL, such as /assets/logo-1a2b3c4d.png. Images, fonts, media, PDFs and .wasm are assets; a .json import stays a module.

HashedA changed file is a changed URL, so a cache cannot serve last week's file
Counted by the buildThe file is in the output because a module imported it — nothing to remember in esdev.json
Not in --libA library cannot know where the consuming build serves a file from, so it refuses

Use assets for a file nothing imports — a favicon.ico or robots.txt — where the name must not change. A relative asset path in a target resolves against the entry module's directory and is copied into the deployment.

Stylesheets and CSS Modules

A <link rel="stylesheet"> is an entry too. It and everything it @imports become one hashed file, and a relative url() is followed, so fonts and images travel with the stylesheet. --minify drops comments and collapses whitespace.

A *.module.css imported from JavaScript is scoped to the file that declares it, and the import resolves to the mapping:

JavaScript
import styles from "./Button.module.css"; // { button: "button_a1b2c3d4" }

Class names, id selectors and @keyframes are renamed; :global(…) opts out. Every module's CSS is collected into one hashed stylesheet and linked from the document, never injected from script.

composes reuses a class from the same file, another module, or global. The mapping's value becomes a transitive list of class names. A .css that is not .module.css is emitted unscoped for third-party stylesheets.

What the CSS pipeline does not do

No syntax lowering or vendor prefixing, no value-level minification, and no per-file typed class names.

Output layout

The browser outdir contains the document plus its hashed assets directory. Dynamic chunks, stylesheets and imported files all live beside the entry so the directory can be served as one deployable unit. For target configuration and safe replacement rules, see Project builds.

Last updated on
Edit this page