Vitus Labs
Tools Rolldown

Tools Rolldown

Rolldown-based build tool for library packages — ESM/CJS/UMD output with DTS generation and platform globals.

@vitus-labs/tools-rolldown is the primary build tool for the vitus-labs ecosystem. It uses Rolldown (a Rust-based bundler) to produce optimized library bundles with automatic TypeScript declaration generation.

Installation

npm install @vitus-labs/tools-rolldown

Quick Start

Add a build script to your package.json:

{
  "scripts": {
    "build": "vl_rolldown_build"
  }
}

Run it:

bun run build

The tool reads your package.json fields (main, module, exports) and generates the appropriate output files in lib/.

How It Works

  1. Reads package.json to determine output formats and entry points
  2. Loads config from vl-tools.config.mjs (via tools-core)
  3. Generates a build pipeline based on exports/main/module fields
  4. Bundles with Rolldown, generates DTS with rolldown-plugin-dts
  5. Injects platform globals (__WEB__, __NATIVE__, etc.)
  6. Outputs source maps, file size stats, and bundle visualization

CLI Commands

vl_rolldown_build

Production build — generates all output formats defined in package.json.

vl_rolldown_build

vl_rolldown_watch

Watch mode — rebuilds on file changes (development).

vl_rolldown_watch

Configuration

Configure via vl-tools.config.mjs under the build key:

// vl-tools.config.mjs
export default {
  build: {
    sourceDir: 'src',           // Input directory (default: 'src')
    outputDir: 'lib',           // Output directory (default: 'lib')
    replaceGlobals: true,       // Inject platform globals (default: true)
    typescript: true,           // Generate .d.ts files (default: true)
    filesize: true,             // Show file sizes in console (default: true)
    visualise: {                // Bundle visualization
      template: 'network',     // 'network' | 'treemap' | 'sunburst'
      gzipSize: true,
      outputDir: 'analysis',
    },
    external: ['react/jsx-runtime'],  // Additional external packages
    exclude: [                  // Patterns to exclude from bundle
      'lib', 'node_modules/**',
      '**/__tests__/**', '**/__stories__/**',
      '*.test.*', '*.stories.*',
    ],
  },
}

Output Format Detection

The tool reads your package.json to determine what to build:

exports Field (Preferred)

{
  "exports": {
    ".": {
      "import": "./lib/index.js",
      "require": "./lib/index.cjs",
      "types": "./lib/index.d.ts"
    }
  }
}
ConditionFormatPlatform
importESMuniversal
requireCJSuniversal
nodeESMnode
defaultESMuniversal

Subpath Exports

Packages with multiple entry points are automatically detected and built:

{
  "exports": {
    ".": {
      "import": "./lib/index.js",
      "types": "./lib/types/index.d.ts"
    },
    "./devtools": {
      "import": "./lib/devtools.js",
      "types": "./lib/types/devtools.d.ts"
    },
    "./validation/zod": {
      "import": "./lib/validation/zod.js",
      "types": "./lib/types/validation/zod.d.ts"
    }
  }
}

Each subpath is resolved to a source file by convention:

  • "."src/index.ts
  • "./devtools"src/devtools (resolves to src/devtools.ts or src/devtools/index.ts)
  • "./validation/zod"src/validation/zod

Separate .d.ts declarations are generated for each subpath with a types field.

Passthrough exports like "./package.json": "./package.json" are automatically skipped (not built).

main / module Fields (Fallback)

{
  "main": "lib/index.cjs",
  "module": "lib/index.js"
}
  • main → CJS (or ESM if type: "module")
  • module → ESM
  • react-native → ESM with native platform

Browser Field

If browser differs from main, separate builds are generated:

{
  "main": "lib/index.js",
  "browser": {
    "./lib/index.js": "./lib/browser.js"
  }
}

Platform Globals

When replaceGlobals: true (default), these constants are injected at build time:

GlobalTypeDescription
__VERSION__stringPackage version from package.json
__WEB__booleantrue for web/node/browser platforms
__NATIVE__booleantrue for React Native
__BROWSER__booleantrue for browser-only
__NODE__booleantrue for Node.js
__CLIENT__booleantrue for browser + native

Platform assignment depends on the build variant:

Platform__WEB____NATIVE____BROWSER____NODE____CLIENT__
universal
browsertruefalsetruefalsetrue
nodetruefalsefalsetruefalse
nativefalsetruefalsefalsetrue
webtruefalsefalsefalsefalse

Type Generation

When typescript: true, the first build in the pipeline generates a rolled-up .d.ts file using rolldown-plugin-dts. The output path is read from package.json:

{
  "exports": {
    ".": {
      "types": "./lib/index.d.ts"
    }
  }
}

For packages with subpath exports, a separate .d.ts file is generated for each subpath that has a types field.

External Dependencies

All dependencies and peerDependencies from package.json are automatically marked as external (not bundled). Additional externals can be added via config:

export default {
  build: {
    external: ['react/jsx-runtime', 'some-other-package'],
  },
}

Bundle Visualization

When visualise is configured, an interactive HTML visualization is generated:

lib/analysis/index.html

Templates: 'network' (default), 'treemap', 'sunburst'

Advanced Build Options

For non-library builds such as Chrome extensions, CLI tools, serverless functions, or Electron apps, you can bypass the package.json-driven pipeline with explicit configuration:

// vl-tools.config.mjs
export default {
  build: {
    // Explicit entries — skips package.json field detection
    entries: [
      { input: 'src/background.ts', file: 'dist/background.js', format: 'iife', env: 'production' },
      { input: 'src/content.ts', file: 'dist/content.js', format: 'iife' },
      { input: 'src/popup.ts', file: 'dist/popup.js', format: 'es' },
    ],

    // Bundle all dependencies (no externals)
    bundleAll: true,

    // Copy static assets after build
    copyFiles: [
      { from: 'src/manifest.json', to: 'dist/manifest.json' },
      { from: 'src/popup.html', to: 'dist/popup.html' },
    ],

    // Inject text at top/bottom of output
    banner: '#!/usr/bin/env node',
    footer: '// Built with @vitus-labs/tools-rolldown',

    // Resolve aliases
    alias: {
      '@': './src',
    },

    // Custom rolldown plugins (appended to built-in ones)
    plugins: [],

    // Disable declarations for non-library builds
    typescript: false,
  },
}
OptionTypeDefaultDescription
entriesArray<{ input, file, format?, env?, platform? }>undefinedExplicit entry points (bypasses package.json detection)
bundleAllbooleanfalseBundle all dependencies instead of externalizing
copyFilesArray<{ from, to }>undefinedCopy static files/directories after build
bannerstringundefinedText injected at the top of each output file
footerstringundefinedText injected at the bottom of each output file
aliasRecord<string, string>undefinedResolve aliases for import remapping
pluginsRolldownPlugin[][]Custom plugins appended to built-in ones

Entry Format Options

Each entry in the entries array supports:

FieldRequiredDefaultValues
inputyesSource file path (e.g., 'src/index.ts')
fileyesOutput file path (e.g., 'dist/index.js')
formatno'es''es', 'cjs', 'umd', 'iife'
envno'development''development', 'production'
platformno'universal''universal', 'browser', 'node', 'native'

Use Cases

  • Chrome extensions — multiple IIFE entries, bundleAll: true, copyFiles for manifest/HTML
  • CLI tools — single entry, banner: '#!/usr/bin/env node', bundleAll: true
  • AWS Lambda — single entry, bundleAll: true, env: 'production'
  • Electron — separate entries for main (node) and renderer (browser)
  • Web Workers — IIFE or ES entry alongside the main app

Build Pipeline

The tool generates multiple builds sequentially. A typical library with ESM + CJS + types produces:

[1/2] Creating build: lib/index.js (ES Module)
[2/2] Creating build: lib/index.cjs (CommonJS)

Each build:

  1. Resolves node modules with platform-specific extensions
  2. Replaces platform globals
  3. Generates source maps
  4. Reports file sizes
  5. Produces visualization (first build only)

On this page