Styling & assets

Everything below works with zero configuration — netpack detects what a file needs from its extension (and, for CSS preprocessing, from what’s installed) rather than requiring a config file.

CSS Ordering

CSS files imported from JavaScript are emitted in the order their importing modules appear in the dependency graph — the cascade consistently matches runtime execution across builds.

CSS output order follows JS evaluation order The order CSS files are imported in JavaScript becomes their order in the output cascade, via each module's post-order index. app.js 1 2 3 import "shared.css" import "b.css" import "c.css" post-order output cascade 1 2 3 shared.css b.css c.css later wins CSS is emitted in JS-evaluation order (the post-order index) — stable across every build.

How it works

During graph traversal each module receives a post-order index after its children resolve, and imports are processed sequentially in declaration order (not in parallel) so indices reflect source position exactly. When a JS module imports multiple CSS files the CSS nodes inherit the relative order of their importers through these indices.

This means:

import './b.css';   // b.css appears first in output
import './c.css';   // c.css appears second

The bundle factory registry is sorted by post-order index so the runtime injects styles in the same order the JS evaluated them, preserving the intended cascade.

Shared CSS chunks

When the same CSS file is imported by multiple entry bundles it is extracted into a shared chunk (common.NNNNN.css). The order of shared chunks in the HTML <link> tags follows the post-order of the first importing bundle, producing a stable cross-entry cascade.

CSS Modules

CSS module imports follow the same ordering as regular CSS imports — the class-name maps are exported and injected in evaluation order.

Conflict detection

A CSS file that appears before another in one entry but after it in another creates an ordering conflict — the two modules have different relative positions across chunk groups and the cascade cannot satisfy both at once.

netpack detects these conflicts during the build and warns on stderr:

[netpack] warning: Conflicting CSS order between shared.css and a.css.
These modules appear in different orders across chunk groups and the
output cascade may differ from source order.

No warning means the computed ordering is consistent across all entries.

Debug output

Set NETPACK_DEBUG_CSS_ORDER=1 to list every CSS file in computed evaluation order:

NETPACK_DEBUG_CSS_ORDER=1 npx netpack bundle src/index.html
[netpack] CSS module order (by JS evaluation):
  1: shared.css
  2: a.css
  3: b.css

CSS Modules

Whether a CSS import is treated as a CSS module depends on how you import it, not the file name:

import './app.css';           // plain global CSS — nothing hashed
import styles from './app.module.css'; // named/default binding — CSS module

Any import with named or default bindings (not just a bare side-effecting import) marks that CSS file as a module: its class selectors get hashed, and the generated JS module exports the original → hashed class name mapping, so:

import styles from './app.css';
// styles.button -> "button_a1b2c3"
<button className={styles.button}>Go</button>

Sass / LESS / PostCSS (incl. Tailwind)

Import a .scss/.sass or .less file the same way as .css — netpack detects the preprocessor from the extension and compiles it before the usual CSS-module/bundling step. PostCSS (and, through it, Tailwind) is picked up automatically when your project has a PostCSS config present; no separate flag needed.

These three are the one place the otherwise-native, no-runtime netpack binary reaches out to Node: preprocessing is delegated to a small long-lived Node helper process that calls the real sass/less/postcss packages. Everything else in this document (plain CSS, CSS Modules) has no such dependency. Practically, this means sass, less or postcss (plus a PostCSS config, for Tailwind) need to be installed in your project — and Node.js available — the moment you import a file that needs them.

Images, other assets and public/

Covered in full in Images & assets — importing an image or any other non-CSS/JSON file, the SkiaSharp-based optimization pass, content hashing, and the public/ folder convention for files that should bypass the bundler entirely.

JSON

import config from './config.json';

Imported directly as a parsed module — no plugin required.