Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions .github/workflows/reusable-release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,23 @@ jobs:
for f in crates/vite_global_cli/Cargo.toml packages/cli/binding/Cargo.toml; do
node -e 'const fs=require("fs"),f=process.argv[1];fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/^version = "[^"]*"/m,`version = "${process.env.VERSION}"`))' "$f"
done
# Stamp packages/cli/package.json too so the bundled JS dist inlines
# ${VERSION} as cliPkg.version (VITE_PLUS_VERSION). Without this a preview
# `vp migrate` pins the build-time release placeholder instead of the
# test build, and skips the registry-bridge .npmrc.
pnpm exec json-edit packages/cli/package.json '_.version = process.env.VERSION'
# Stamp the package.json files too:
# - packages/cli: the bundled JS dist inlines ${VERSION} as
# cliPkg.version (VITE_PLUS_VERSION). Without this a preview
# `vp migrate` pins the build-time release placeholder instead of
# the test build, and skips the registry-bridge .npmrc.
# - packages/core: the release build bakes ${VERSION} into bundled
# Rolldown's binding version guards, matching the
# @voidzero-dev/vite-plus-<platform> packages published as
# ${VERSION}. A release checkout already carries ${VERSION}; this
# only changes preview builds.
# The list feeds the verify step below, so a stamped file cannot
# silently drop out of verification.
PKG_JSON_STAMP_FILES="packages/cli/package.json packages/core/package.json"
for f in $PKG_JSON_STAMP_FILES; do
pnpm exec json-edit "$f" '_.version = process.env.VERSION'
done
echo "PKG_JSON_STAMP_FILES=$PKG_JSON_STAMP_FILES" >> "$GITHUB_ENV"

- name: Verify version replacement
shell: bash
Expand All @@ -84,9 +96,11 @@ jobs:
echo "ERROR: version stamp failed for $f"; head -5 "$f"; exit 1
fi
done
if [ "$(node -p "require('./packages/cli/package.json').version")" != "${VERSION}" ]; then
echo "ERROR: version stamp failed for packages/cli/package.json"; exit 1
fi
for f in ${PKG_JSON_STAMP_FILES}; do
if [ "$(node -p "require('./$f').version")" != "${VERSION}" ]; then
echo "ERROR: version stamp failed for $f"; exit 1
fi
done
echo "Version stamp verified: ${VERSION}"

- name: Build
Expand Down
68 changes: 33 additions & 35 deletions packages/cli/BUNDLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,16 @@ packages/cli/

The CLI builds native bindings for the following platform targets:

| Target | Platform | Architecture | Output File |
| --------------------------- | -------- | ------------ | --------------------------------- |
| `aarch64-apple-darwin` | macOS | ARM64 | `vite-plus.darwin-arm64.node` |
| `x86_64-apple-darwin` | macOS | x64 | `vite-plus.darwin-x64.node` |
| `aarch64-unknown-linux-gnu` | Linux | ARM64 | `vite-plus.linux-arm64-gnu.node` |
| `x86_64-unknown-linux-gnu` | Linux | x64 | `vite-plus.linux-x64-gnu.node` |
| `aarch64-pc-windows-msvc` | Windows | ARM64 | `vite-plus.win32-arm64-msvc.node` |
| `x86_64-pc-windows-msvc` | Windows | x64 | `vite-plus.win32-x64-msvc.node` |
| Target | Platform | Architecture | Output File |
| ---------------------------- | -------- | ------------ | --------------------------------- |
| `aarch64-apple-darwin` | macOS | ARM64 | `vite-plus.darwin-arm64.node` |
| `x86_64-apple-darwin` | macOS | x64 | `vite-plus.darwin-x64.node` |
| `aarch64-unknown-linux-gnu` | Linux | ARM64 glibc | `vite-plus.linux-arm64-gnu.node` |
| `aarch64-unknown-linux-musl` | Linux | ARM64 musl | `vite-plus.linux-arm64-musl.node` |
| `x86_64-unknown-linux-gnu` | Linux | x64 glibc | `vite-plus.linux-x64-gnu.node` |
| `x86_64-unknown-linux-musl` | Linux | x64 musl | `vite-plus.linux-x64-musl.node` |
| `aarch64-pc-windows-msvc` | Windows | ARM64 | `vite-plus.win32-arm64-msvc.node` |
| `x86_64-pc-windows-msvc` | Windows | x64 | `vite-plus.win32-x64-msvc.node` |

These targets are defined in `package.json` under the `napi.targets` field.

Expand Down Expand Up @@ -220,29 +222,21 @@ await cli.build({

### Module Specifier Rewriting

During release builds, the core package rewrites all `@rolldown/binding-*` imports to point to `vite-plus/binding`:

```typescript
// In packages/core/build.ts
if (process.env.RELEASE_BUILD) {
// @rolldown/binding-darwin-arm64 → vite-plus/binding
source = source.replace(/@rolldown\/binding-([a-z0-9-]+)/g, 'vite-plus/binding');
}
```
During release builds, the core package rewrites each supported `@rolldown/binding-*` import to the matching Vite+ platform package (see `packages/core/build-support/rewrite-rolldown-binding.ts`):

**Transformation examples**:

| Original Import | After Rewrite |
| ---------------------------------- | ------------------- |
| `@rolldown/binding-darwin-arm64` | `vite-plus/binding` |
| `@rolldown/binding-linux-x64-gnu` | `vite-plus/binding` |
| `@rolldown/binding-win32-x64-msvc` | `vite-plus/binding` |
| Original Import | After Rewrite |
| ---------------------------------- | ---------------------------------------- |
| `@rolldown/binding-darwin-arm64` | `@voidzero-dev/vite-plus-darwin-arm64` |
| `@rolldown/binding-linux-x64-gnu` | `@voidzero-dev/vite-plus-linux-x64-gnu` |
| `@rolldown/binding-win32-x64-msvc` | `@voidzero-dev/vite-plus-win32-x64-msvc` |

This means:

1. The bundled rolldown code in `@voidzero-dev/vite-plus-core/rolldown` resolves native bindings from `vite-plus/binding`
1. The bundled rolldown code in `@voidzero-dev/vite-plus-core/rolldown` resolves native bindings through core's own declared optional dependencies (injected at publish time by `publish-native-addons.ts`)
2. Users don't need to install separate `@rolldown/binding-*` platform packages
3. The single `.node` file contains both vite-plus task runner and rolldown bindings
3. The platform `.node` file contains both vite-plus task runner and rolldown bindings

### Native Binding Contents

Expand All @@ -259,23 +253,27 @@ When compiled with `RELEASE_BUILD=1`, the `.node` file contains:
User imports 'vite-plus/rolldown'
→ packages/cli re-exports from @voidzero-dev/vite-plus-core/rolldown
→ packages/core/dist/rolldown/index.mjs
→ Native binding: vite-plus/binding (rewritten from @rolldown/binding-*)
→ binding/vite-plus.darwin-arm64.node (contains rolldown_binding)
→ Native binding: @voidzero-dev/vite-plus-darwin-arm64
(rewritten from @rolldown/binding-darwin-arm64)
→ vite-plus.darwin-arm64.node (contains rolldown_binding)
```

### Platform-Specific Publishing

Native bindings are published as separate platform packages for optimal install size:

| Platform | Published Package |
| ----------- | ----------------------------------------- |
| macOS ARM64 | `@voidzero-dev/vite-plus-darwin-arm64` |
| macOS x64 | `@voidzero-dev/vite-plus-darwin-x64` |
| Linux ARM64 | `@voidzero-dev/vite-plus-linux-arm64-gnu` |
| Linux x64 | `@voidzero-dev/vite-plus-linux-x64-gnu` |
| Windows x64 | `@voidzero-dev/vite-plus-win32-x64-msvc` |

These are automatically installed via `optionalDependencies` based on the user's platform.
| Platform | Published Package |
| ----------------- | ------------------------------------------ |
| macOS ARM64 | `@voidzero-dev/vite-plus-darwin-arm64` |
| macOS x64 | `@voidzero-dev/vite-plus-darwin-x64` |
| Linux ARM64 glibc | `@voidzero-dev/vite-plus-linux-arm64-gnu` |
| Linux ARM64 musl | `@voidzero-dev/vite-plus-linux-arm64-musl` |
| Linux x64 glibc | `@voidzero-dev/vite-plus-linux-x64-gnu` |
| Linux x64 musl | `@voidzero-dev/vite-plus-linux-x64-musl` |
| Windows ARM64 | `@voidzero-dev/vite-plus-win32-arm64-msvc` |
| Windows x64 | `@voidzero-dev/vite-plus-win32-x64-msvc` |

These are automatically installed via `optionalDependencies` based on the user's platform. `publish-native-addons.ts` injects the exact-pinned entries into both `vite-plus` (via napi-rs prePublish) and `@voidzero-dev/vite-plus-core` during publish; the committed package.json files carry none of them.

See `publish-native-addons.ts` for the publishing pipeline.

Expand Down
5 changes: 0 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@
"./bin": {
"import": "./dist/bin.js"
},
"./binding": {
"types": "./binding/index.d.cts",
"import": "./binding/index.cjs",
"require": "./binding/index.cjs"
},
"./fmt": {
"types": "./dist/fmt.d.ts",
"import": "./dist/fmt.js"
Expand Down
50 changes: 38 additions & 12 deletions packages/cli/publish-native-addons.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
import { execSync } from 'node:child_process';
import {
copyFileSync,
existsSync,
chmodSync,
mkdirSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { copyFileSync, existsSync, chmodSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { readdir } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

import { NapiCli, parseTriple } from '@napi-rs/cli';

import pkg from './package.json' with { type: 'json' };
import { editJsonFile } from './src/utils/json.ts';
import { editJsonFile, readJsonFile } from './src/utils/json.ts';

const cli = new NapiCli();

Expand Down Expand Up @@ -94,6 +86,41 @@ for (const dir of platformDirs) {
}));
}

// Fresh read: napi-rs prePublish rewrote this package.json on disk, so the
// top-level `pkg` import is stale for injected fields.
const cliPackageJson = readJsonFile(join(currentDir, 'package.json')) as {
version: string;
repository?: unknown;
optionalDependencies?: Record<string, string>;
};

// napi-rs prePublish injects the platform packages into this package's
// `optionalDependencies`. Release builds of core rewrite bundled Rolldown's
// binding requires to the same platform packages (see
// packages/core/build-support/rewrite-rolldown-binding.ts), so core must
// declare them too; napi-rs manages a single package, so mirror the injected
// entries into core with identical pins. Like the CLI's entries, these live
// only in the publish working tree, never in the committed package.json.
const nativePlatformPins: Record<string, string> = {};
for (const target of pkg.napi.targets) {
const packageName = `${pkg.napi.packageName}-${parseTriple(target).platformArchABI}`;
const pin = cliPackageJson.optionalDependencies?.[packageName];
if (!pin) {
console.error(
`napi prePublish did not inject ${packageName} into packages/cli/package.json optionalDependencies`,
);
process.exit(1);
}
nativePlatformPins[packageName] = pin;
}
editJsonFile(join(repoRoot, 'packages', 'core', 'package.json'), (corePkgJson) => ({
...corePkgJson,
optionalDependencies: {
...(corePkgJson.optionalDependencies as Record<string, string> | undefined),
...nativePlatformPins,
},
}));

// Publish each NAPI platform package (without vp binary)
const npmTag = process.env.NPM_TAG || 'latest';
if (!skipNpmPublish) {
Expand Down Expand Up @@ -121,8 +148,7 @@ if (!skipNpmPublish) {
}
}

// Read version from packages/cli/package.json for lockstep versioning
const cliPackageJson = JSON.parse(readFileSync(join(currentDir, 'package.json'), 'utf-8'));
// Lockstep versioning: the CLI platform packages publish at the same version.
const cliVersion = cliPackageJson.version;

// Create and publish separate @voidzero-dev/vite-plus-cli-{platform} packages
Expand Down
41 changes: 15 additions & 26 deletions packages/core/BUNDLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ await cp(join(rolldownPluginUtilsDir, 'dist'), join(projectDir, 'dist', 'pluginu

- `@rolldown/pluginutils` → `@voidzero-dev/vite-plus-core/rolldown/pluginutils`
- `rolldown/*` → `@voidzero-dev/vite-plus-core/rolldown/*`
- In release builds: `@rolldown/binding-*` → `vite-plus/binding`
- In release builds: supported `@rolldown/binding-*` → `@voidzero-dev/vite-plus-*`

**Input**: `rolldown/packages/rolldown/dist/`
**Output**: `dist/rolldown/`
Expand Down Expand Up @@ -142,45 +142,34 @@ Located in `build-support/rewrite-module-specifiers.ts`, this utility rewrites s

### Release Build: Native Binding Rewriting

During release builds (`RELEASE_BUILD=1`), an additional critical transformation occurs for Rolldown's native bindings:
During release builds (`RELEASE_BUILD=1`), `bundleRolldown()` rewrites Rolldown's native binding requires through `build-support/rewrite-rolldown-binding.ts`. For every platform in the CLI's `napi.targets` (mapped to napi platform suffixes with `parseTriple`), the loader's `@rolldown/binding-<suffix>` requires become `@voidzero-dev/vite-plus-<suffix>`, and that branch's version guard switches from the Rolldown version to core's version, which is what the Vite+ platform packages are published as.

```typescript
// 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**, one per `napi.targets` entry in `packages/cli/package.json` (see the target table in [CLI Package Bundling](../cli/BUNDLING.md#napi-targets)), for example:

**Platform-specific binding rewrites**:
| Original Import | Rewritten Import |
| ---------------------------------- | ---------------------------------------- |
| `@rolldown/binding-darwin-arm64` | `@voidzero-dev/vite-plus-darwin-arm64` |
| `@rolldown/binding-linux-x64-musl` | `@voidzero-dev/vite-plus-linux-x64-musl` |

| 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` |
Specifiers for platforms Vite+ does not ship (android, freebsd, the `wasm32-wasi` fallback, `darwin-universal`, ...) stay on `@rolldown/binding-*` and keep their upstream version guards. The build fails if the rewritten specifier and guard counts diverge from the napi-rs loader shape, so a loader format change cannot ship a partial rewrite.

**Why this matters**:

1. **Self-contained distribution** - Users don't need to install separate `@rolldown/binding-*` packages
2. **Version alignment** - The rolldown binding version is synced to the vite-plus version
3. **Single native module** - The `vite-plus/binding` export points to the CLI's compiled `.node` file which includes `rolldown_binding` when built with `RELEASE_BUILD=1`
2. **Declared dependency graph** - Core resolves the binding through its own `optionalDependencies` (injected during publish by `packages/cli/publish-native-addons.ts`), so pnpm's global virtual store and Yarn PnP work without hidden hoisting, and core no longer requires back into `vite-plus`
3. **Version alignment** - Each rewritten branch's guard checks the platform package version against core's version; both are published lockstep

**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)
requires '@voidzero-dev/vite-plus-<platform>'
(rewritten from '@rolldown/binding-<platform>')
vite-plus.<platform>.node (contains rolldown_binding)
```

See [CLI Package Bundling](../cli/BUNDLING.md#rolldown-native-binding-integration) for details on how the CLI compiles rolldown bindings.
See [CLI Package Bundling](../cli/BUNDLING.md#rolldown-native-binding-integration) for details on how the CLI compiles and publishes the platform packages, and `rfcs/core-binding-resolution.md` for the design.

---

Expand Down Expand Up @@ -350,7 +339,7 @@ dist/
# Build the core package
pnpm -C packages/core build

# Release build (rewrites @rolldown/binding-* to vite-plus/binding)
# Release build (rewrites supported @rolldown/binding-* to @voidzero-dev/vite-plus-*)
RELEASE_BUILD=1 pnpm -C packages/core build
```

Expand Down
Loading
Loading