voidzero-dev / voidzero-dev/vite-plus

esmExternalRequirePlugin (builtin:esm-external-require) has no effect when building through vp run

Open
#1,063 2 comments 1 reaction 1 assignee Claimed by @fengmk2 View on GitHub
Dominant language
Rust
Stars
5.8k
Forks
262
Avg merge
1d 34m
Merged PRs (30d)
135

Description

## Summary

`esmExternalRequirePlugin` from Vite 8 does not take effect when building through `vp run`, because vp uses its own bundled Vite CLI (`@voidzero-dev/vite-plus-core/dist/vite/node/cli.js`) instead of the project's `vite/bin/vite.js`. The plugin is registered and visible in the plugin list, but the Rolldown native-layer transformation that converts CJS `require()` calls to ESM `import` statements never executes. This results in `require()` calls remaining in the output, which breaks deployment to ESM-only runtimes like Cloudflare Workers.

## Environment

- **vite-plus**: v0.1.12
- **vite**: v8.0.0
- **rolldown**: v1.0.0-rc.9
- **Node.js**: v24.14.0 (also reproduced on v22.16.0)
- **OS**: Linux (Ubuntu 24.04, tested on both GitHub Actions and devcontainer)
- **Target**: Cloudflare Workers (ESM-only runtime)

## Reproduction

### Minimal vite.config.ts

```typescript
import { cloudflare } from '@cloudflare/vite-plugin';
import { defineConfig, esmExternalRequirePlugin } from 'vite';

export default defineConfig({
plugins: [
esmExternalRequirePlugin(),
cloudflare({
configPath: './wrangler.jsonc',
persistState: true,
})
],
});
```

The project has CJS dependencies (e.g., `pg` / node-postgres) that use `require()` for Node.js builtins (`events`, `crypto`, `util`, `dns`, `fs`).

### Steps

```bash
# 1. Build with vite directly — works correctly
rm -rf dist && npx vite build
grep -c 'require(' dist/output/index.js
# → 0

# 2. Build with vp — require() calls remain
rm -rf dist && npx vp run --filter "@my/package" build
grep -c 'require(' dist/output/index.js
# → 24
```

## Expected Behavior

`vp run ... build` should produce the same output as `npx vite build`. Specifically, `esmExternalRequirePlugin` should convert all CJS `require()` calls for external modules into ESM `import` statements.

## Actual Behavior

`vp run ... build` produces output containing 24 `require()` calls — Rolldown's `__require()` shim wrapping Node.js builtins from CJS dependencies:

```javascript
var __require = /* @__PURE__ */ createRequire(import.meta.url);
// ...
__require("util")
__require("crypto")
__require("fs")
__require("dns")
__require("events")
```

Deploying this to Cloudflare Workers fails with:

```
Uncaught Error: Calling `require` for "events" in an environment
that doesn't expose the `require` function.
[code: 10021]
```

## Root Cause Analysis

### The plugin is registered but the native transform does not execute

I instrumented the `buildStart` hook to inspect the plugin pipeline in both code paths:

| | `npx vite build` | `npx vp run ... build` |
|---|---|---|
| **Vite CLI binary** | `vite@8.0.0/bin/vite.js` | `vite-plus-core@0.1.12/dist/vite/node/cli.js` |
| **Total plugins** | 45 | 45 |
| **`builtin:esm-external-require` registered** | Yes | Yes |
| **Modules transformed** | 622 | 612 |
| **Output size** | 1,109.01 kB | 1,106.65 kB |
| **`require()` calls in output** | **0** | **24** |

The 10-module difference (622 vs 612) corresponds exactly to the synthetic import-wrapper modules that `esmExternalRequirePlugin` generates when working correctly.

### What differs

The only observable differences between the two code paths:

1. **The Vite CLI entry point**: vp uses its own bundled copy at `@voidzero-dev/vite-plus-core/dist/vite/node/cli.js` rather than the project's `vite/bin/vite.js`.
2. **One extra env var**: vp sets `VITE_PLUS_VERSION=0.1.12`.

Everything else is identical: same cwd, same NODE_ENV, same config file, same plugin count, same plugin names.

### Hypothesis

`esmExternalRequirePlugin` registers a Rolldown builtin plugin (`builtin:esm-external-require`) that operates in Rolldown's native (Rust) layer. When vp's bundled Vite CLI invokes Rolldown, the builtin plugin identifier is likely not being correctly resolved or passed through to the native layer. The plugin object exists in the JavaScript plugin array (hence it shows up in `buildStart` introspection), but the native-side transformation that actually rewrites the `require()` calls is never triggered.

## Workaround

Use `npx vite build` directly instead of `vp run ... build` for affected packages.

## Impact

This is a **build-correctness** issue that is **silent at build time** — no warnings or errors are emitted, and the build completes successfully. The problem only manifests at deploy or runtime. Any project using `vp run` + `esmExternalRequirePlugin` + an ESM-only runtime will produce broken output.

## Minimal Reproduction Repository

https://github.com/shoya-kimura/vite-plus-repro-1063

```bash
git clone https://github.com/shoya-kimura/vite-plus-repro-1063
cd vite-plus-repro-1063
pnpm install

# Works — 0 require() in output
rm -rf dist && npx vite build
grep -c 'require(' dist/vp_repro/index.js # → 0

# Broken — 24 require() in output
rm -rf dist && npx vp run build
grep -c 'require(' dist/vp_repro/index.js # → 24
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.