GitHub - FlorianRappl/netpack: A web project bundler written in C#/.NET.

GitHub

9 min read Original article ↗

netpack is an experiment to see whether .NET tooling can compete with bundlers written in Rust or Go. Thanks to Ahead-of-Time (AoT) compilation it starts at native speed — no JIT to warm up, no runtime to install — while staying arguably more readable than Rust and more capable than Go.

Warning

netpack is pre-1.0 and experimental. It is not production ready, and the chance it works end-to-end for your project today is low. If you like the idea, contribute code or sponsor the project 🍻.

Highlights

  • Zero-config — point it at an index.html (like Vite or Parcel) or a .js/.ts entry directly.
  • Native startup — an AoT-compiled CLI; competitive with esbuild and faster than rspack/Vite on the benchmarks below.
  • Batteries included — TypeScript, JSX, CSS & CSS Modules, Sass/LESS/PostCSS (incl. Tailwind), images (optimized with SkiaSharp), JSON, HTML and arbitrary assets, out of the box.
  • Frameworks — React (with Fast Refresh), Preact, Solid, Vue (a native SFC compiler), Svelte, Astro and vanilla.
  • A real bundler — tree-shaking, minification, source maps, code-splitting, a dev server with true HMR, and a bundle analyzer.
  • Flexible output — ESM / CommonJS / UMD / SystemJS (--format), web / Node / Deno targets (--platform), import maps, externals, shared dependencies, and Module + Native Federation.
  • Its own toolchain — a hand-written JavaScript/TypeScript/JSX tokenizer, parser, printer, mangler and tree-shaker in C# — no Acornima, Babel or SWC.

See the documentation for the details behind each of these.

Performance

Bundler performance comparison

The whole project is mostly geared towards performance. The key idea is that .NET with AoT (Ahead-of-Time compilation, i.e., no runtime dependency) can be on the same level as Go or Rust. While it still contains a GC (garbage collector, i.e., automatic memory management), the startup time is much improved. There is no JIT that is required to be run, which instantly yields good performance.

Test esbuild rspack Vite netpack
Small lib 326ms 611ms 601ms 359ms
Small project 670ms 912ms 1658ms 418ms
Medium project 1931ms 2877ms 10601ms 974ms
Large project 2189ms 2422ms 13710ms 1357ms

Besides performance there are other reasons for choosing C#/.NET. It's arguably more readable than Rust, more powerful than Go, and better performing than JavaScript. The ecosystem, however, is lacking.

Another reason for having another bundler (but in .NET) is that it could be used natively within the .NET ecosystem, e.g., the post-process or optimize ASP.NET Core and / or Blazor web applications.

Benchmark Details All benchmarks have been performed using [hyperfine](https://github.com/sharkdp/hyperfine) on Ubuntu 22.04 using an AMD Ryzen 7 3700X 8-Core processor with 32 GB of memory.

The benchmarks used netpack v0.0.1, rspack v1.1.8, esbuild v0.24.0, and Vite v6.0.1.

Lib project

netpack:

Benchmark 1: npx netpack bundle src/lib/index.mjs --minify
  Time (mean ± σ):     359.4 ms ±  18.3 ms    [User: 383.4 ms, System: 56.1 ms]
  Range (min … max):   335.4 ms … 386.5 ms    10 runs

esbuild:

Benchmark 1: npx esbuild --bundle src/lib/index.mjs --format=esm --outdir=dist
  Time (mean ± σ):     326.3 ms ±  19.4 ms    [User: 361.5 ms, System: 35.9 ms]
  Range (min … max):   305.1 ms … 361.8 ms    10 runs

rspack:

Benchmark 1: npx rspack build --config rspack.lib.mjs
  Time (mean ± σ):     611.0 ms ±  27.9 ms    [User: 626.4 ms, System: 109.2 ms]
  Range (min … max):   559.1 ms … 643.7 ms    10 runs

Vite:

Benchmark 1: npx vite build
  Time (mean ± σ):     601.8 ms ±  20.5 ms    [User: 603.1 ms, System: 87.5 ms]
  Range (min … max):   573.5 ms … 633.5 ms    10 runs

Small project

netpack:

Benchmark 1: npx netpack bundle src/small/index.html --minify
  Time (mean ± σ):     418.5 ms ±  30.3 ms    [User: 440.1 ms, System: 71.4 ms]
  Range (min … max):   388.2 ms … 475.7 ms    10 runs

esbuild:

Benchmark 1: node esbuild.small.mjs
  Time (mean ± σ):     670.3 ms ±  30.8 ms    [User: 728.2 ms, System: 57.9 ms]
  Range (min … max):   610.1 ms … 711.4 ms    10 runs

rspack:

Benchmark 1: npx rspack build --config rspack.small.mjs
  Time (mean ± σ):     912.8 ms ±  39.0 ms    [User: 1067.3 ms, System: 223.6 ms]
  Range (min … max):   866.0 ms … 1002.9 ms    10 runs

Vite:

Benchmark 1: npx vite build
  Time (mean ± σ):      1.658 s ±  0.096 s    [User: 2.467 s, System: 0.162 s]
  Range (min … max):    1.495 s …  1.827 s    10 runs

Medium project

netpack:

Benchmark 1: npx netpack bundle src/medium/index.html --minify
  Time (mean ± σ):     974.9 ms ±  65.2 ms    [User: 1409.9 ms, System: 398.3 ms]
  Range (min … max):   854.2 ms … 1070.1 ms    10 runs

esbuild:

Benchmark 1: node esbuild.medium.mjs
  Time (mean ± σ):      1.931 s ±  0.107 s    [User: 1.480 s, System: 0.091 s]
  Range (min … max):    1.779 s …  2.149 s    10 runs

rspack:

Benchmark 1: npx rspack build --config rspack.medium.mjs
  Time (mean ± σ):      2.877 s ±  0.117 s    [User: 6.919 s, System: 1.451 s]
  Range (min … max):    2.677 s …  3.025 s    10 runs

Vite:

Benchmark 1: npx vite build
  Time (mean ± σ):     10.601 s ±  0.229 s    [User: 13.159 s, System: 2.226 s]
  Range (min … max):   10.219 s … 11.014 s    10 runs

Large project

netpack:

Benchmark 1: npx netpack bundle src/large/index.html --minify
  Time (mean ± σ):      1.357 s ±  0.043 s    [User: 2.553 s, System: 0.716 s]
  Range (min … max):    1.286 s …  1.433 s    10 runs

esbuild:

Benchmark 1: node esbuild.large.mjs
  Time (mean ± σ):      2.189 s ±  0.097 s    [User: 1.391 s, System: 0.091 s]
  Range (min … max):    2.057 s …  2.334 s    10 runs

Vite:

Benchmark 1: npx vite build
  Time (mean ± σ):     13.710 s ±  0.236 s    [User: 17.474 s, System: 2.342 s]
  Range (min … max):   13.301 s … 14.237 s    10 runs

rspack:

Benchmark 1: npx rspack build --config rspack.large.mjs
  Time (mean ± σ):      2.422 s ±  0.133 s    [User: 8.956 s, System: 1.381 s]
  Range (min … max):    2.283 s …  2.651 s    10 runs

Installation

Add netpack to your Node.js project via your favorite package manager, e.g.:

Now you can run netpack like:

npx netpack bundle src/index.html

Generally, any index.html as for Vite or Parcel should work fine as an entry point. You don't need to supply an HTML file. You can also provide a JavaScript file directly:

npx netpack bundle src/main.mjs

You can start the dev server with the serve command:

npx netpack serve src/index.html

The dev server watches the file system and rebuilds on change. It hot-reloads modules where it can (with React Fast Refresh when react-refresh is installed) and falls back to a full browser reload otherwise.

Supported Architectures

Documentation

Usage documentation lives in the docs/ folder (and powers the docs site at netpack.anglevisions.com/docs):

Repository structure

src/                      the .NET solution (NetPack.sln)
  NetPack/                the bundler and CLI
    Syntax/               hand-written JS/TS/JSX tokenizer, parser, printer, mangler, tree-shaker
    Graph/                module graph, resolution, bundles, output formats, platforms
    Commands/             the bundle / serve / analyze CLI verbs
    Assets/               image optimization (SkiaSharp) and other asset handling
    Server/               the dev server (watch + reload/HMR)
    NodeJs.cs             Node IPC bridge for JS-only tools (Sass, LESS, PostCSS, Svelte)
  NetPack.Tests/          the xUnit test suite
  npm/                    npm packaging: the `netpack` wrapper and per-platform `@netpack/*` binaries
  resources/              embedded runtime assets (bundle analyzer UI, federation remote)
docs/                     user documentation (source for the docs site)
www/                      the marketing site (www/home) and docs site (www/docs), both Astro
data/                     example projects used for manual testing and benchmarks
art/                      logo and brand assets
.github/workflows/        CI (build + test) and the release pipeline

netpack compiles to a single native, AoT-published binary per platform; the npm netpack package is a thin wrapper that picks the right @netpack/<platform> binary at install time.

Overview

The following items are features or topics that are relevant for bundlers - netpack in particular.

General Features

  • Handle JavaScript
  • Handle JSX (configurable factory via tsconfig.json jsxFactory or a @jsx pragma; defaults to React.createElement)
  • Handle TypeScript
  • Handle images (optimizes them using SkiaSharp)
  • Handle any asset
  • Handle CSS
  • Handle CSS modules (named/default import of a .css file hashes its class names and exports the original→hashed map)
  • Handle Vue SFCs (.vue; native block splitting via AngleSharp)
  • Handle PostCSS (incl. Tailwind)
  • Handle SASS
  • Handle LESS
  • Handle HTML
  • Handle JSON
  • Handle codegen
  • Handle custom file types with loaders

Frameworks

  • Angular
  • Astro
  • React
  • Preact
  • Solid (requires babel-preset-solid installed; compiled via the Node bridge)
  • Svelte (requires svelte installed; compiled via the Node bridge)
  • Vanilla
  • Vue (single-file components; see notes below)

Vue and Astro components are compiled natively (no Node round-trip); Svelte is compiled by the installed Svelte compiler, and Solid's JSX by babel-preset-solid, over the Node bridge. See Vue, Astro, Svelte and Solid for exactly what's supported and the current limitations.

Bundler Basics

  • Sourcemaps
  • Minification
  • DevServer with reload
  • Bundle analyzer (start with analyze)
  • Image / asset variants (e.g., width/height optimized)
  • Copy public assets ("public" folder below entry-HTML)
  • Externals (--external react react-dom; works also automatically if an importmap is present)
  • True HMR (not just refresh)

More Advanced Topics

  • Importmap support
  • Shared dependencies (--shared react react-dom; automatically adds an importmap if HTML is used)
  • Module Federation support
  • Native Federation support (federation.json with "kind": "native")
  • React Fast Refresh support (dev server; requires react-refresh installed)
  • Custom JSX factory
  • Platforms (web, node, deno; --platform)
  • Tree shaking
  • Other formats (esm, cjs, umd, systemjs; --format)
  • Presets / configuration system (JSONC netpack.json / --preset; composable, with hooks — see Configuration)

Idea Stash

Integration ideas / explorations: