Skip to content

feat(plugin-vite): enable main process hot restart via exported API - #4168

Closed
bglgwyng wants to merge 52 commits into
electron:nextfrom
bglgwyng:feat/hot-restart-exported-api
Closed

feat(plugin-vite): enable main process hot restart via exported API#4168
bglgwyng wants to merge 52 commits into
electron:nextfrom
bglgwyng:feat/hot-restart-exported-api

Conversation

@bglgwyng

@bglgwyng bglgwyng commented Mar 16, 2026

Copy link
Copy Markdown

Add restartApp()/onAppRestart() to @electron-forge/core-utils as an explicit API for triggering Electron app restarts. The Vite plugin calls restartApp() in its closeBundle hook when the main process bundle is rebuilt. The start API registers the actual restart logic via onAppRestart(). The existing rs stdin handler also goes through restartApp() now, so there's a single restart path.

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project follows, as appropriate.
  • The changes are appropriately documented (if applicable).
  • The changes have sufficient test coverage (if applicable).
  • The testsuite passes successfully on my local machine (if applicable).

Summarize your changes:

Background

Main process hot restart for the Vite plugin has been requested and attempted multiple times:

What this PR does

Implements the approach suggested in #3380 and unblocks the commented-out code in #3583:

  1. Adds restartApp() and onAppRestart() to @electron-forge/core-utils, backed by a module-scoped EventEmitter. Plugins call restartApp() to signal a restart; the start API registers the actual kill→respawn logic via onAppRestart().
  2. Replaces the commented-out process.stdin.emit('data', 'rs') in pluginHotRestart with restartApp().
  3. The existing rs stdin handler also calls restartApp() instead of duplicating the restart logic, so both paths converge.

dependabot Bot and others added 30 commits November 24, 2025 23:23
Bumps [actions/stale](https://github.com/actions/stale) from 10.0.0 to 10.1.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](actions/stale@3a9db7e...5f858e3)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: 10.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…on#4042)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@ea165f8...330a01c)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…tron#4041)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@634f93c...018cc2c)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…4040)

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@a26af69...e797f83)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.4.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@49933ea...2028fbc)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@08c6903...1af3b93)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…4063)

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@e797f83...83679a8)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.2.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](digitalbazaar/forge@v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* docs: add `next` README notice

Added important notice about upcoming feature development and pre-releases.

* zzz
* fix(template): add `private: true`

* add regression tests
Bumps [jws](https://github.com/brianloveswords/node-jws) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/brianloveswords/node-jws/releases)
- [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md)
- [Commits](auth0/node-jws@v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: jws
  dependency-version: 4.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* test(slow): test api against `file:` protocol fixtures (electron#4087)

* test(slow): simplify bootstrapping tests (electron#4096)

* test: use verdaccio for e2e init testing (electron#4100)

* zzz

* gg
…tron#4110)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@018cc2c...37930b1)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…on#4111)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@330a01c...b7c566a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v6...395ad32)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v6...8e8c483)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@0057852...9255dc7)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* test(import): use verdaccio

* remove fixtures

* move fuses to verdaccio too...
* fix: apply correct version bump commit title

* Revert "chore: version bump (electron#4120)"

This reverts commit 4c32ab9.
* Revert "chore: bump version to 7.11.0 (electron#4123)"

This reverts commit fa6672a.

* chore: use yarn `workspace:*` cross-references (electron#4056)

* i don't think we need to initlink anymore because we're publishing to verdaccio

* add install deps command back in

* fix lockfile

* another approach
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.1 to 5.0.3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@9255dc7...cdf6c1f)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.2.7 to 3.4.1.
- [Commits](WebReflection/flatted@v3.2.7...v3.4.1)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
BlackHole1
BlackHole1 previously approved these changes Mar 23, 2026
bglgwyng and others added 3 commits March 23, 2026 16:02
Add restartApp()/onAppRestart() to @electron-forge/core-utils as an
explicit API for triggering Electron app restarts. The Vite plugin
calls restartApp() in its closeBundle hook when the main process
bundle is rebuilt. The start API registers the actual restart logic
via onAppRestart(). Also backport the duplicate restart guard
(!lastSpawned.restarted) to the stdin handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stdin 'rs' handler now calls restartApp() instead of duplicating
the kill→respawn logic, so all restart requests flow through the
single onAppRestart callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent unnecessary terminal cursor manipulation when the Electron app
has not been spawned yet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bglgwyng
bglgwyng force-pushed the feat/hot-restart-exported-api branch from 6c0c50e to 76aceb1 Compare March 23, 2026 07:02
@bglgwyng

Copy link
Copy Markdown
Author

Do we need more approvals to merge this PR?

@erickzhao erickzhao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing: Can we make this opt-in via the plugin config instead of the default behaviour?

I would also maybe target next instead of main because I'm currently trying to get the next major version of Forge to a more stable beta and releases go out faster. I think this would be a great addition!

@bglgwyng
bglgwyng changed the base branch from main to next April 7, 2026 05:39
@bglgwyng
bglgwyng dismissed BlackHole1’s stale review April 7, 2026 05:39

The base branch was changed.

@github-actions github-actions Bot added the next label Apr 7, 2026
Add `hotRestart` option to VitePluginConfig (default: false).
The main process restart is now only enabled when explicitly configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bglgwyng
bglgwyng force-pushed the feat/hot-restart-exported-api branch from c3d7b26 to 190c34c Compare April 7, 2026 05:41
# Conflicts:
#	.eslintignore
#	.github/workflows/ci.yml
#	.github/workflows/gh-pages.yml
#	.github/workflows/release.yml
#	.gitignore
#	lerna.json
#	package.json
#	packages/api/cli/package.json
#	packages/api/core/package.json
#	packages/api/core/spec/slow/make.slow.spec.ts
#	packages/api/core/spec/slow/package.slow.spec.ts
#	packages/external/create-electron-app/package.json
#	packages/external/create-electron-app/spec/fixture/custom_init/index.cjs
#	packages/maker/appx/package.json
#	packages/maker/base/package.json
#	packages/maker/deb/package.json
#	packages/maker/dmg/package.json
#	packages/maker/flatpak/package.json
#	packages/maker/msix/package.json
#	packages/maker/pkg/package.json
#	packages/maker/rpm/package.json
#	packages/maker/snap/package.json
#	packages/maker/squirrel/package.json
#	packages/maker/wix/package.json
#	packages/maker/zip/package.json
#	packages/plugin/auto-unpack-natives/package.json
#	packages/plugin/base/package.json
#	packages/plugin/fuses/package.json
#	packages/plugin/fuses/spec/FusesPlugin.slow.verdaccio.spec.ts
#	packages/plugin/local-electron/package.json
#	packages/plugin/vite/package.json
#	packages/plugin/webpack/package.json
#	packages/publisher/base-static/package.json
#	packages/publisher/base/package.json
#	packages/publisher/bitbucket/package.json
#	packages/publisher/electron-release-server/package.json
#	packages/publisher/gcs/package.json
#	packages/publisher/github/package.json
#	packages/publisher/nucleus/package.json
#	packages/publisher/s3/package.json
#	packages/publisher/snapcraft/package.json
#	packages/template/base/package.json
#	packages/template/base/tmpl/package.json
#	packages/template/vite-typescript/package.json
#	packages/template/vite-typescript/spec/ViteTypeScriptTemplate.slow.verdaccio.spec.ts
#	packages/template/vite/package.json
#	packages/template/webpack-typescript/package.json
#	packages/template/webpack-typescript/spec/WebpackTypeScript.slow.verdaccio.spec.ts
#	packages/template/webpack/package.json
#	packages/utils/core-utils/package.json
#	packages/utils/core-utils/src/index.ts
#	packages/utils/test-utils/package.json
#	packages/utils/tracer/package.json
#	packages/utils/types/package.json
#	packages/utils/web-multi-logger/package.json
#	tools/verdaccio/spawn-verdaccio.ts
#	tools/version.sh
#	vitest.workspace.mts
#	yarn.lock
@bglgwyng
bglgwyng requested review from BlackHole1 and erickzhao April 7, 2026 05:54
Comment thread .github/workflows/add-to-project.yml
@bglgwyng
bglgwyng requested a review from MarshallOfSound April 7, 2026 06:37
* @param callback - A callback function that returns the value of the new `package.json` to be applied
* @returns The original `package.json` prior to mutation
*/
export async function updatePackageJSON(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like a duplicate

@erickzhao

Copy link
Copy Markdown
Member

Hey @bglgwyng, sorry for the disruption. It looks like changing the base branch messed up the PR state and added a bunch of unrelated changes into the diff. Would it be possible to re-apply the commits from your original PR onto a fresh copy of next to clean it up? 😢

@bglgwyng

bglgwyng commented Apr 9, 2026

Copy link
Copy Markdown
Author

Replaced by #4210 with a clean branch rebased on next.

@bglgwyng bglgwyng closed this Apr 9, 2026
@erickzhao

Copy link
Copy Markdown
Member

Thank you!

tzh476 added a commit to tzh476/forge that referenced this pull request Sep 4, 2026
Re-applies the three commits from electron#4317 onto a fresh copy of `next`, which is the procedure
@erickzhao asked for on electron#4168 ("re-apply the commits from your original PR onto a fresh copy of
`next`") rather than flipping the base ref, which he reported "messed up the PR state and added a
bunch of unrelated changes into the diff".

Adapted to `next` rather than copied verbatim:

- `vite.renderer.config.ts` — kept `next`'s ESM import convention (`./vite.base.config.js`) and added
  the new import with the same `.js` suffix. This is the one line where the two branches disagree, so
  it was merged by hand rather than by patch.
- `README.md` — `next` has gained a "Main process hot restart" section since electron#4317 was opened. Both
  sections are kept; the conflict was only positional.
- The new `src/config/vite.node-integration.config.ts` has no relative imports, so nothing needed the
  `.js` suffix there. `next`'s spec files use bare relative imports, so the new spec matches them
  as-is.

Includes the `ServiceWorkerMain` fix: `electronExportNames` had omitted it, and a missing name is a
hard build failure that Rollup reports identically to a name that does not exist.

Verified on `next` (no device, and the repo's own suite needs a full install that would not fit in
the disk available, so this is a direct exercise of the plugin's logic):

    electron shim / node:fs shim / node:path shim         PASS
    no __vite-browser-external                            PASS
    no bare ESM specifier left in the output              PASS
    ServiceWorkerMain resolvable                          PASS

The check is a discriminator, not a smoke test: with the plugin removed the same build fails with
`"readFileSync" is not exported by "__vite-browser-external"`, which is the defect this change fixes.

Change-Id: I95721bb54785fc5bc9142ecafc70cf26a5a510fc
Assisted-by: Claude (Anthropic)
tzh476 added a commit to tzh476/forge that referenced this pull request Sep 4, 2026
Re-applies the three commits from electron#4317 onto a fresh copy of `next`, which is the procedure
@erickzhao asked for on electron#4168 ("re-apply the commits from your original PR onto a fresh copy of
`next`") rather than flipping the base ref, which he reported "messed up the PR state and added a
bunch of unrelated changes into the diff".

`next` is where features land: of the last 200 merged PRs, 17 of 17 `feat`-titled ones targeted
`next` and none targeted `main`.

Adapted to `next` rather than copied verbatim:

- `vite.renderer.config.ts` -- kept `next`'s ESM import convention (`./vite.base.config.js`) and
  added the new import with the same `.js` suffix.
- `README.md` -- `next` has gained a "Main process hot restart" section since electron#4317 was opened.
  Both sections are kept; the conflict was only positional.
- The new `src/config/vite.node-integration.config.ts` has no relative imports, so nothing needed
  the `.js` suffix there.

`next` also swaps the bundler and Electron major (`vite ^6` / Rollup / `electron ^39` ->
`vite ^8` / Rolldown / `electron ^42`), which required four fixes that are no-ops on `main`:

- drop `output.freeze`: Rolldown has no such option. Grepping the whole `rolldown@1.0.0-rc.12`
  package for `freeze` returns zero hits (control term `entryFileNames` hits five files), so it
  does not freeze namespace objects at all and the opt-out is unnecessary, not merely untyped.
- `resolveId` now marks the shim's own `require` external instead of resolving it back to the
  shim. The self-reference was silent: `__esmMin`'s `fn = 0` guard swallows the re-entrant init
  call rather than overflowing the stack, so every Electron export became `undefined` at runtime
  in an otherwise-green build.
- drop the `runtimeRequire` alias -- Rolldown rewrites only syntactically direct `require()`
  calls, so the aliased call was dropped from the output entirely.
- add `sharedTexture`, which Electron 42 exports and 39 did not.

The production-build assertion no longer hardcodes one bundler's output spelling: Rollup emits a
direct `require("electron")` while Rolldown wraps it in an interop helper that reads
`require.apply(this, arguments)`, so a regex anchored on `require(` reports a false negative on a
correct bundle. It now asserts that the specifier survives to a runtime require.

Verified on CI against this branch, on the repo's own matrix: `lint-and-build` green and
`fast-tests` green on ubuntu-22.04, macos-latest and windows-latest -- 438 passed, 0 failed,
including all four node-integration specs. Mutation-checked: making the self-reference guard
never fire turns `preserves Node and Electron imports in production builds` red (1 failed /
437 passed), so the test is a real discriminator rather than a smoke test.

Co-authored-by: rafael81 <36774+rafael81@users.noreply.github.com>
Assisted-by: Claude (Anthropic)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants