This document explains how @voidzero-dev/vite-plus-core bundles multiple upstream projects into a single unified package.
The core package uses a multi-project bundling strategy that combines 5 upstream projects:
| Project | Source Location | Purpose |
|---|---|---|
@rolldown/pluginutils |
rolldown/packages/pluginutils |
Rolldown plugin utilities |
rolldown |
rolldown/packages/rolldown |
Rolldown bundler |
vite |
vite/packages/vite |
Vite v8 beta |
tsdown |
node_modules/tsdown |
TypeScript build tool |
vitepress |
node_modules/vitepress |
Documentation tool |
This approach enables users to access Vite, Rolldown, and related tools through a single package with consistent module specifier rewrites.
The build process executes 6 steps in sequence:
Action: Copies pre-built dist directory.
await cp(join(rolldownPluginUtilsDir, 'dist'), join(projectDir, 'dist', 'pluginutils'), {
recursive: true,
});Input: rolldown/packages/pluginutils/dist/
Output: dist/pluginutils/
Action: Copies dist directory and rewrites module specifiers.
Transformations:
@rolldown/pluginutils→@voidzero-dev/vite-plus-core/rolldown/pluginutilsrolldown/*→@voidzero-dev/vite-plus-core/rolldown/*- In release builds:
@rolldown/binding-*→vite-plus/binding
Input: rolldown/packages/rolldown/dist/
Output: dist/rolldown/
Action: Full Rolldown build with custom transforms.
This is the most complex step, using the upstream vite-rolldown.config with modifications:
- Filter externals - Bundles
picomatch,tinyglobby,fdir,rolldown,yamlinstead of keeping them external - Add RewriteImportsPlugin - Rewrites vite/rolldown imports at build time
- Rewrite static paths - Fixes
VITE_PACKAGE_DIR,CLIENT_ENTRY,ENV_ENTRYconstants - Copy additional files -
misc/,.d.tsfiles,types/,client.d.ts
Input: vite/packages/vite/
Output: dist/vite/
Action: Re-bundles tsdown with CJS dependency handling.
Process:
- Bundle
tsdown/dist/run.mjsandtsdown/dist/index.mjsusing Rolldown - Detect third-party CJS modules using
find-create-require.ts - Bundle detected CJS dependencies using
build-cjs-deps.ts - Bundle type declarations using
rolldown-plugin-dts
Input: node_modules/tsdown/dist/
Output: dist/tsdown/
Action: Copies dist directory and rewrites vite imports.
Transformations:
vite→@voidzero-dev/vite-plus-core/vite
Input: node_modules/vitepress/
Output: dist/vitepress/
Action: Merges metadata from upstream packages and records bundled versions.
Updates:
peerDependencies- Merged from tsdown and vitepeerDependenciesMeta- Merged from tsdown and vitebundledVersions- Records vite, rolldown, and tsdown versions
The build uses two complementary rewriting mechanisms:
Located in build-support/rewrite-imports.ts, this Rolldown plugin rewrites imports during bundling:
export const RewriteImportsPlugin: Plugin = {
name: 'rewrite-imports-for-vite-plus',
resolveId: {
order: 'pre',
handler(id: string) {
if (id.startsWith('vite/')) {
return { id: id.replace(/^vite\//, `${pkgJson.name}/`), external: true };
}
if (id === 'rolldown') {
return { id: `${pkgJson.name}/rolldown`, external: true };
}
if (id.startsWith('rolldown/')) {
return { id: id.replace(/^rolldown\//, `${pkgJson.name}/rolldown/`), external: true };
}
},
},
};Located in build-support/rewrite-module-specifiers.ts, this utility rewrites specifiers in already-built files using AST-grep:
| Original Import | Rewritten Import |
|---|---|
vite |
@voidzero-dev/vite-plus-core |
vite/* |
@voidzero-dev/vite-plus-core/* |
rolldown |
@voidzero-dev/vite-plus-core/rolldown |
rolldown/* |
@voidzero-dev/vite-plus-core/rolldown/* |
@rolldown/pluginutils |
@voidzero-dev/vite-plus-core/rolldown/pluginutils |
@rolldown/pluginutils/* |
@voidzero-dev/vite-plus-core/rolldown/pluginutils/* |
During release builds (RELEASE_BUILD=1), an additional critical transformation occurs for Rolldown's native bindings:
// In bundleRolldown()
if (process.env.RELEASE_BUILD) {
// @rolldown/binding-darwin-arm64 → vite-plus/binding
source = source.replace(/@rolldown\/binding-([a-z0-9-]+)/g, 'vite-plus/binding');
// Sync version strings
source = source.replaceAll(`${rolldownBindingVersion}`, pkgJson.version);
}Platform-specific binding rewrites:
| Original Import | Rewritten Import |
|---|---|
@rolldown/binding-darwin-arm64 |
vite-plus/binding |
@rolldown/binding-darwin-x64 |
vite-plus/binding |
@rolldown/binding-linux-arm64-gnu |
vite-plus/binding |
@rolldown/binding-linux-x64-gnu |
vite-plus/binding |
@rolldown/binding-win32-x64-msvc |
vite-plus/binding |
Why this matters:
- Self-contained distribution - Users don't need to install separate
@rolldown/binding-*packages - Version alignment - The rolldown binding version is synced to the vite-plus version
- Single native module - The
vite-plus/bindingexport points to the CLI's compiled.nodefile which includesrolldown_bindingwhen built withRELEASE_BUILD=1
Resolution chain:
User code imports '@voidzero-dev/vite-plus-core/rolldown'
→ dist/rolldown/index.mjs
→ imports 'vite-plus/binding' (rewritten from @rolldown/binding-*)
→ vite-plus CLI package ./binding export
→ binding/vite-plus.darwin-arm64.node (contains rolldown_binding)
See CLI Package Bundling for details on how the CLI compiles rolldown bindings.
Tsdown uses createRequire() to load some CommonJS dependencies. These are detected and bundled specially:
Uses oxc-parser to find patterns like:
// Pattern 1: Static import
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
require('some-cjs-package');
// Pattern 2: Global module
const require = globalThis.process.getBuiltinModule('module').createRequire(import.meta.url);
require('some-cjs-package');Creates CJS entry files and bundles them with Rolldown:
// Creates: npm_entry_some_cjs_package.cjs
module.exports = require('some-cjs-package');The original require("some-cjs-package") calls are rewritten to require("./npm_entry_some_cjs_package.cjs").
dist/
├── pluginutils/ # @rolldown/pluginutils
│ ├── index.js
│ ├── index.d.ts
│ └── filter/
├── rolldown/ # Rolldown bundler
│ ├── index.mjs
│ ├── index.d.mts
│ ├── config.mjs
│ ├── experimental-index.mjs
│ ├── filter-index.mjs
│ ├── parallel-plugin.mjs
│ ├── parse-ast-index.mjs
│ ├── plugins-index.mjs
│ └── ...
├── vite/ # Vite
│ ├── node/
│ │ ├── index.js
│ │ ├── index.d.ts
│ │ ├── internal.js
│ │ ├── module-runner.js
│ │ └── chunks/
│ ├── client/
│ │ ├── client.mjs
│ │ └── env.mjs
│ ├── misc/
│ ├── types/
│ └── client.d.ts
├── tsdown/ # TypeScript build tool
│ ├── index.js
│ ├── index-types.d.ts
│ ├── run.js
│ └── npm_entry_*.cjs # Bundled CJS deps
└── vitepress/ # Documentation tool
├── dist/
├── types/
├── client.d.ts
├── theme.d.ts
└── theme-without-fonts.d.ts
| Export Path | Points To | Description |
|---|---|---|
. |
./dist/vite/node/index.js |
Vite main entry |
./client |
types: ./dist/vite/client.d.ts |
Client ambient types |
./dist/client/* |
./dist/vite/client/* |
Client runtime files |
./internal |
./dist/vite/node/internal.js |
Internal Vite APIs |
./lib |
./dist/tsdown/index.js |
Tsdown library |
./module-runner |
./dist/vite/node/module-runner.js |
Vite module runner |
./rolldown |
./dist/rolldown/index.mjs |
Rolldown main entry |
./rolldown/config |
./dist/rolldown/config.mjs |
Rolldown config helpers |
./rolldown/experimental |
./dist/rolldown/experimental-index.mjs |
Experimental features |
./rolldown/filter |
./dist/rolldown/filter-index.mjs |
Filter utilities |
./rolldown/parallelPlugin |
./dist/rolldown/parallel-plugin.mjs |
Parallel plugin support |
./rolldown/parseAst |
./dist/rolldown/parse-ast-index.mjs |
AST parsing |
./rolldown/plugins |
./dist/rolldown/plugins-index.mjs |
Built-in plugins |
./rolldown/pluginutils |
./dist/pluginutils/index.js |
Plugin utilities |
./rolldown/pluginutils/filter |
./dist/pluginutils/filter/index.js |
Filter utilities |
./types/* |
./dist/vite/types/* |
Type definitions |
| Upstream Project | Source Location | Relation |
|---|---|---|
@rolldown/pluginutils |
../../rolldown/packages/pluginutils |
Git submodule |
rolldown |
../../rolldown/packages/rolldown |
Git submodule |
vite |
../../vite/packages/vite |
Git submodule |
tsdown |
node_modules/tsdown |
npm dependency |
vitepress |
node_modules/vitepress |
npm dependency |
| Package | Purpose |
|---|---|
rolldown |
Bundler for building vite and tsdown |
rolldown-plugin-dts |
TypeScript declaration bundling |
@ast-grep/napi |
Post-build module specifier rewriting |
oxc-parser |
CJS require detection in tsdown |
oxfmt |
Code formatting for package.json |
tinyglobby |
File globbing for copying files |
- Update the
vitegit submodule to the new version - Run
pnpm -C packages/core build - Verify
bundledVersions.viteinpackage.jsonis updated - Test with
pnpm test
- Update the
rolldowngit submodule to the new version - Run
pnpm -C packages/core build - Verify
bundledVersions.rolldowninpackage.jsonis updated - Test with
pnpm test
- Update
tsdownversion indevDependencies - Run
pnpm install - Run
pnpm -C packages/core build - Check for new CJS dependencies (build will detect them automatically)
- Verify
bundledVersions.tsdowninpackage.jsonis updated - Test with
pnpm test
- Update
vitepressversion indevDependencies - Run
pnpm install - Run
pnpm -C packages/core build - Test documentation builds
# Build the core package
pnpm -C packages/core build
# Release build (rewrites @rolldown/binding-* to vite-plus/binding)
RELEASE_BUILD=1 pnpm -C packages/core build1. bundleRolldownPluginutils() Copy pre-built dist
2. bundleRolldown() Copy + rewrite module specifiers
3. buildVite() Full Rolldown build with transforms
├── Apply RewriteImportsPlugin Build-time import rewriting
├── Apply rewrite-static-paths Fix VITE_PACKAGE_DIR constants
├── Run Rolldown build Bundle vite source
└── Copy and rewrite .d.ts files Post-build specifier rewriting
4. bundleTsdown() Re-bundle with CJS handling
├── Bundle tsdown with Rolldown Find CJS modules
├── buildCjsDeps() Bundle detected CJS deps
└── Bundle types with dts plugin Generate declarations
5. bundleVitepress() Copy + rewrite vite imports
6. mergePackageJson() Merge metadata + record versions
// Source directories
const rolldownPluginUtilsDir = resolve(
projectDir,
'..',
'..',
'rolldown',
'packages',
'pluginutils',
);
const rolldownSourceDir = resolve(projectDir, '..', '..', 'rolldown', 'packages', 'rolldown');
const rolldownViteSourceDir = resolve(projectDir, '..', '..', 'vite', 'packages', 'vite');
const tsdownSourceDir = resolve(projectDir, 'node_modules/tsdown');
// Package name used for rewrites
const targetPackage = '@voidzero-dev/vite-plus-core';The bundledVersions field in package.json records the exact versions of bundled upstream projects:
{
"bundledVersions": {
"vite": "8.0.0-beta.8",
"rolldown": "1.0.0-beta.60",
"tsdown": "0.20.0-beta.4"
}
}This is automatically updated by mergePackageJson() during each build.