Skip to content

Instantly share code, notes, and snippets.

@indexzero
Last active February 4, 2026 03:16
Show Gist options
  • Select an option

  • Save indexzero/87fe42c6e8f9840d7ba1650d2a89ed20 to your computer and use it in GitHub Desktop.

Select an option

Save indexzero/87fe42c6e8f9840d7ba1650d2a89ed20 to your computer and use it in GitHub Desktop.
rspack build analysis

How Rspack Downloads the Correct Binary for Each Platform

Rspack uses npm's optional dependencies feature combined with platform filtering to ensure users only download the binary for their specific OS and architecture.

The Mechanism: Optional Dependencies + Platform Filtering

1. Optional Dependencies in @rspack/binding

The main @rspack/binding package (crates/node_binding/package.json) declares all platform-specific packages as optionalDependencies:

"optionalDependencies": {
  "@rspack/binding-darwin-arm64": "workspace:*",
  "@rspack/binding-darwin-x64": "workspace:*",
  "@rspack/binding-linux-x64-gnu": "workspace:*",
  "@rspack/binding-linux-x64-musl": "workspace:*",
  "@rspack/binding-win32-x64-msvc": "workspace:*",
  // ... etc
}

2. Platform-Specific Package Metadata

Each platform package (in /npm/**/package.json) declares os, cpu, and libc constraints:

// @rspack/binding-darwin-arm64
"publishConfig": {
  "os": ["darwin"],
  "cpu": ["arm64"]
}

// @rspack/binding-linux-x64-gnu
"publishConfig": {
  "os": ["linux"],
  "cpu": ["x64"],
  "libc": ["glibc"]
}

// @rspack/binding-linux-x64-musl
"publishConfig": {
  "os": ["linux"],
  "cpu": ["x64"],
  "libc": ["musl"]
}

npm automatically skips optional dependencies that don't match the current platform's os/cpu/libc.

3. Runtime Resolution in binding.js

At runtime, binding.js uses a fallback chain to load the correct binary:

// 1. First tries local .node file (for dev builds)
require('./rspack.darwin-arm64.node')

// 2. Falls back to npm package
require('@rspack/binding-darwin-arm64')

// 3. Falls back to WASI if native fails
require('@rspack/binding-wasm32-wasi')

The code detects:

  • Platform: process.platform (darwin, linux, win32, freebsd, etc.)
  • Architecture: process.arch (x64, arm64, ia32, etc.)
  • Libc: Custom isMusl() function that checks /usr/bin/ldd, process reports, or runs ldd --version

Flow Summary

npm install @rspack/core
    └── installs @rspack/binding (with optionalDependencies)
        └── npm checks os/cpu/libc constraints
            └── only downloads matching platform package
                e.g., @rspack/binding-darwin-arm64 on M1 Mac

At runtime:
    require('@rspack/binding')
        └── binding.js detects platform
            └── requires @rspack/binding-darwin-arm64
                └── loads rspack.darwin-arm64.node

Supported Platforms

Based on the packages in /npm/:

Package OS CPU Libc
@rspack/binding-darwin-arm64 macOS ARM64 (Apple Silicon) -
@rspack/binding-darwin-x64 macOS x64 (Intel) -
@rspack/binding-linux-x64-gnu Linux x64 glibc
@rspack/binding-linux-x64-musl Linux x64 musl (Alpine)
@rspack/binding-linux-arm64-gnu Linux ARM64 glibc
@rspack/binding-linux-arm64-musl Linux ARM64 musl (Alpine)
@rspack/binding-win32-x64-msvc Windows x64 -
@rspack/binding-win32-arm64-msvc Windows ARM64 -
@rspack/binding-win32-ia32-msvc Windows x86 (32-bit) -
@rspack/binding-wasm32-wasi Any Any - (WASI fallback)

Notes

  • This is a standard pattern used by many Rust+Node.js projects (esbuild, swc, lightningcss, etc.)
  • The mechanism is generated and managed by NAPI-RS
  • The WASI package serves as a universal fallback for unsupported platforms

How the Rust Build Works in Rspack

Based on my exploration of the codebase, here's how Rspack's Rust build process works:

Architecture Overview

Rspack uses NAPI-RS to bridge Rust code with Node.js. The architecture consists of:

  1. Rust Core (crates/rspack_core and other crates): The bundler's core logic written in Rust
  2. Node Binding (crates/node_binding): NAPI-RS binding layer that exposes Rust functionality to Node.js
  3. JavaScript Layer (packages/rspack): Node.js package that wraps the native binding

Build Process

1. Build Scripts

The build process is orchestrated through npm scripts in package.json:

  • pnpm run build:binding:dev: Builds the Rust binding in development mode
  • pnpm run build:binding:release: Builds for production
  • pnpm run build:js: Builds the JavaScript packages
  • pnpm run build:cli:dev: Combines both steps (binding + JS)

2. NAPI Build Script

The actual build happens in /crates/node_binding/scripts/build.js:

// Key build command
napi build --platform --dts napi-binding.d.ts --no-js

This script:

  • Uses the @napi-rs/cli tool to compile Rust code
  • Generates TypeScript definitions from Rust
  • Supports multiple build profiles (dev, release, profiling)
  • Configures features like:
    • plugin: Enables plugin support
    • browser: For WASM browser builds
    • tracy-client: For profiling
    • color-backtrace: For better error messages

3. Cargo Configuration

The workspace is defined in /Cargo.toml with:

  • 91 crates in the workspace
  • Shared dependencies and lint configurations
  • Rust 2024 edition

The binding crate (/crates/node_binding/Cargo.toml) is configured as:

  • crate-type = ["cdylib"]: Builds a C-compatible dynamic library
  • Re-exports rspack_binding_api which contains the actual NAPI bindings

4. Multi-Platform Support

The build supports multiple targets through NAPI configuration:

  • Native targets: x64/arm64 for macOS, Windows, Linux
  • WASM targets: wasm32-wasip1-threads for browser/Node.js
  • Cross-compilation: Using Zig when USE_ZIG is set

5. Build Optimizations

For release builds:

  • Uses -Zbuild-std to rebuild stdlib with optimizations
  • Strips unnecessary symbols
  • Disables unwind tables on non-Windows platforms
  • Configures logging level to info-only

Build Commands Summary

# Development build
pnpm run build:cli:dev

# Release build for all platforms
pnpm run build:cli:release:all

# WASM build
pnpm run build:cli:dev:wasm

# Platform-specific builds
pnpm run build:cli:release:arm64  # ARM64
pnpm run build:cli:release:x64    # x86_64

The build process is quite sophisticated, leveraging NAPI-RS to provide high-performance Rust code with a clean JavaScript API surface, while supporting multiple platforms including WebAssembly for browser environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment