Pre-flight checklist
Forge version
7.11.2
Electron version
42.3.2
Operating system
macOS 26.5.2 arm64
Expected behavior
@electron-forge/plugin-vite can be loaded in a project whose node_modules uses a strict, non-hoisted layout.
Actual behavior
The package requires Vite at runtime but declares it only in devDependencies.
These files all require('vite') at runtime:
dist/VitePlugin.js
dist/ViteConfig.js
dist/config/vite.main.config.js
dist/config/vite.preload.config.js
dist/config/vite.renderer.config.js
With a hoisted node_modules, this resolves by accident through the application's own copy of Vite. With a strict layout it does not resolve at all, the plugin cannot be loaded, and electron-forge start and electron-forge package both fail.
Reproduced with Bun 1.4.0 isolated installs:
$ node -e "require('@electron-forge/plugin-vite')"
Error: Cannot find module 'vite'
Require stack:
- .../@electron-forge/plugin-vite/dist/VitePlugin.js
$ bun -e "require('@electron-forge/plugin-vite')"
error: Cannot find package 'vite' from
'.../@electron-forge/plugin-vite/dist/VitePlugin.js'
Bun's isolated linker symlinks each package into a content-addressed store outside the project. Node and Bun resolve a symlink to its real path before they walk parent directories, so the resolution chain starts in the store and never reaches the application's root node_modules. The install.hoist fallback directory, hoistPattern and publicHoistPattern do not change this; all three were tried. The globalStore setting does not change it either. The same reasoning applies to pnpm and to Yarn PnP.
Steps to reproduce
-
Create an empty directory with this package.json:
{
"name": "repro",
"private": true,
"devDependencies": {
"@electron-forge/plugin-vite": "7.11.2",
"vite": "^8.0.16"
}
}
-
Add this bunfig.toml:
[install]
linker = "isolated"
-
Run bun install.
-
Run node -e "require('@electron-forge/plugin-vite')".
Suggested fix
Add a peer dependency on Vite in packages/plugin/vite/package.json:
"peerDependencies": {
"vite": ">=5"
}
A peer dependency rather than a regular dependency, because the application owns the Vite version. Forge reads the user's vite.*.config.ts through loadConfigFromFile, so a pinned regular dependency would install a second Vite and parse the config with the wrong major. vite stays in devDependencies for the package's own tests.
This is also constraint-clean: enforceConsistentDependenciesAcrossTheProject in yarn.config.cjs skips peerDependencies and already exempts vite by name.
Pre-flight checklist
Forge version
7.11.2
Electron version
42.3.2
Operating system
macOS 26.5.2 arm64
Expected behavior
@electron-forge/plugin-vitecan be loaded in a project whosenode_modulesuses a strict, non-hoisted layout.Actual behavior
The package requires Vite at runtime but declares it only in
devDependencies.These files all
require('vite')at runtime:dist/VitePlugin.jsdist/ViteConfig.jsdist/config/vite.main.config.jsdist/config/vite.preload.config.jsdist/config/vite.renderer.config.jsWith a hoisted
node_modules, this resolves by accident through the application's own copy of Vite. With a strict layout it does not resolve at all, the plugin cannot be loaded, andelectron-forge startandelectron-forge packageboth fail.Reproduced with Bun 1.4.0 isolated installs:
Bun's isolated linker symlinks each package into a content-addressed store outside the project. Node and Bun resolve a symlink to its real path before they walk parent directories, so the resolution chain starts in the store and never reaches the application's root
node_modules. Theinstall.hoistfallback directory,hoistPatternandpublicHoistPatterndo not change this; all three were tried. TheglobalStoresetting does not change it either. The same reasoning applies to pnpm and to Yarn PnP.Steps to reproduce
Create an empty directory with this
package.json:{ "name": "repro", "private": true, "devDependencies": { "@electron-forge/plugin-vite": "7.11.2", "vite": "^8.0.16" } }Add this
bunfig.toml:Run
bun install.Run
node -e "require('@electron-forge/plugin-vite')".Suggested fix
Add a peer dependency on Vite in
packages/plugin/vite/package.json:A peer dependency rather than a regular dependency, because the application owns the Vite version. Forge reads the user's
vite.*.config.tsthroughloadConfigFromFile, so a pinned regular dependency would install a second Vite and parse the config with the wrong major.vitestays indevDependenciesfor the package's own tests.This is also constraint-clean:
enforceConsistentDependenciesAcrossTheProjectinyarn.config.cjsskipspeerDependenciesand already exemptsviteby name.