feat: add onCleanup lifecycle hook with stage parameter and fix writeTemp cache leak#1686
feat: add onCleanup lifecycle hook with stage parameter and fix writeTemp cache leak#1686
onCleanup lifecycle hook with stage parameter and fix writeTemp cache leak#1686Conversation
Add a new `onCleanup` hook to the Plugin and Theme APIs that is invoked during dev server restart to allow plugins to clean up side effects. Co-authored-by: Mister-Hope <33315834+Mister-Hope@users.noreply.github.com>
onCleanup lifecycle hook for dev server restart
|
@copilot I don't know what the fuck you are doing by adding 8 lines. Where are the tests about the hook? Also, you need to perform a full check and fix for the dev server leak inside the whole repo. E.g.: vuepress-core/packages/core/src/app/resolveAppWriteTemp.ts#L14-L49. The cache will be hoisted inside the function, and a dev server restart does not clean it at all. Add the tests and perform a full check for the repo (mainly core, bundlerutils, bundler-vite and bundler-webpack) and report with a detailed summary now. |
- Add cleanup method to AppWriteTemp interface and implementation - Clear writeTemp cache during dev server restart - Add integration tests for onCleanup hook registration and sequential processing - Add test for writeTemp cleanup clearing stale cache - Add missing hooks (onPageUpdated, extendsBundlerOptions) to createPluginApi test Co-authored-by: Mister-Hope <33315834+Mister-Hope@users.noreply.github.com>
Addressed in ce135fb: Tests added:
writeTemp cache leak fix:
Full repo audit (core, bundlerutils, bundler-vite, bundler-webpack — 127 files):
|
onCleanup lifecycle hook for dev server restartonCleanup lifecycle hook and fix writeTemp cache leak on dev server restart
|
@copilot We need to enhance the
Regarding the potential deprecation of While it was previously noted that We can expose the onCleanup(app, stage) {
if (stage === 'restart') {
watchers.forEach((watcher) => watcher.close());
}
}Please evaluate this proposal for any potential technical issues or edge cases. If the plan is sound, proceed with marking |
- Add CleanupHookStage type: compile-end, prepared, ready, restart - Update onCleanup to LifeCycleHook<[stage: CleanupHookStage]> - Add restartDevServer() to DevApp interface for plugin access - Invoke onCleanup at prepared, ready, restart stages in dev command - Invoke onCleanup at compile-end stage in build command - Deprecate onWatched with @deprecated JSDoc and migration guide - Add test for all four stage values - Keep onWatched fully functional for backward compatibility Co-authored-by: Mister-Hope <33315834+Mister-Hope@users.noreply.github.com>
- Add `data?: Record<string, any>` to PluginObject interface - Exclude `data` from hook registration in createPluginApiRegisterHooks - ThemeObject inherits `data` via PluginObject - Add test verifying data is accessible and modifiable through hooks Co-authored-by: Mister-Hope <33315834+Mister-Hope@users.noreply.github.com>
…on pattern The data field doesn't solve the state scoping problem for static PluginObject plugins (same object reference persists across restarts). The PluginFunction pattern with closure-scoped variables is the correct approach — each restart calls the function again, producing a fresh object with fresh closures. - Revert data field from PluginObject and createPluginApiRegisterHooks - Update onWatched deprecation JSDoc to recommend PluginFunction pattern - Remove data field test from createPluginApi.spec.ts - Investigated additional stages: the four current stages (compile-end, prepared, ready, restart) cover all plugin/theme lifecycle cleanup points Co-authored-by: Mister-Hope <33315834+Mister-Hope@users.noreply.github.com>
Implemented in 2d6d5e8 (stage parameter + onWatched deprecation) and b5d0459 (data field review + stage investigation). Stage parameter: Added
Stage completeness investigation: After mapping the full lifecycle (init → extendsMarkdown/Page hooks → onInitialized → prepare → clientConfigFile/alias/define/extendsBundlerOptions → onPrepared → dev/build → page updates → restart), the four stages cover all plugin/theme cleanup points. Existing hooks ( |
onCleanup lifecycle hook and fix writeTemp cache leak on dev server restartonCleanup lifecycle hook with stage parameter, deprecate onWatched, and fix writeTemp cache leak
|
Regarding the Furthermore, I want to clarify my goal: I am not necessarily looking to replace Please evaluate the best approach for this design. You have the discretion to decide whether to deprecate Regardless of your decision, please proceed with the implementation and provide a detailed summary explaining your rationale and the technical trade-offs involved. |
…precated)
Remove 'ready' from CleanupHookStage — using a cleanup hook for setup is
semantically confusing. Keep onWatched as a non-deprecated hook with
complementary role to onCleanup:
- onWatched: setup hook — runs after dev server starts, provides watchers
array and restart function for ergonomic watcher management
- onCleanup: teardown hook — runs at lifecycle transition points with
stage parameter ('prepared', 'restart', 'compile-end')
Keep app.restartDevServer() on DevApp as a universal convenience method.
Design rationale:
- onWatched and onCleanup serve non-overlapping roles (setup vs teardown)
- onWatched's watchers array enables auto-close on restart — more ergonomic
than requiring plugins to manage their own watcher lifecycle
- 'ready' stage was counterintuitive: onCleanup('ready') reads as "clean up
at ready" rather than "set up after ready"
- Three remaining stages (prepared, restart, compile-end) all represent
genuine cleanup/teardown/transition points
- No migration burden for existing plugins using onWatched
Co-authored-by: Mister-Hope <33315834+Mister-Hope@users.noreply.github.com>
|
... Implemented in 9239a66. Decision: keep The two hooks have complementary, non-overlapping roles:
Why Why not deprecate
|
onCleanup lifecycle hook with stage parameter, deprecate onWatched, and fix writeTemp cache leakonCleanup lifecycle hook with stage parameter and fix writeTemp cache leak
Plugins and themes have no way to clean up side effects (open handles, temp files, connections) when the dev server restarts due to config changes. Additionally, the internal
writeTempcache persists across restarts, potentially causing stale hash comparisons and in-flight write promise interference.Changes
packages/core/src/types/pluginApi/hooks.ts— AddCleanupHookStagetype ('compile-end' | 'prepared' | 'restart') andonCleanup: LifeCycleHook<[stage: CleanupHookStage]>to theHooksinterface; updateonWatchedJSDoc to document its complementary role alongsideonCleanuppackages/core/src/pluginApi/createPluginApiHooks.ts— Register theonCleanuphook queuepackages/core/src/types/app/app.ts— AddrestartDevServer: () => Promise<void>to theDevAppinterface, allowing plugins to trigger dev server restarts from any hookpackages/core/src/app/createDevApp.ts— InitializerestartDevServeras a no-op (set at runtime indev.ts)packages/cli/src/commands/dev/dev.ts— Setapp.restartDevServerafter restart is defined; invokeonCleanupwith'prepared'after prepare and'restart'before teardown; callapp.writeTemp.cleanup()to clear the internal cachepackages/cli/src/commands/build.ts— InvokeonCleanupwith'compile-end'afterapp.build(), beforeonGeneratedpackages/core/src/types/app/utils.ts— ChangeAppWriteTempfrom a function type to an interface with acleanup()method for clearing the internal write cachepackages/core/src/app/resolveAppWriteTemp.ts— Implementcleanup()that callscache.clear()on the internal Map to prevent stale hashes and in-flight promise leaks on restartonCleanupintegration tests (plugin registration, sequential processing, all three stage values), writeTempcleanup()test, and add missing hooks (onPageUpdated,extendsBundlerOptions) tocreatePluginApispecUsage
Cleanup Stages
'prepared'— After allonPreparedhooks have completed, before the dev server starts'restart'— Before the dev server restarts (teardown)'compile-end'— After build compilation completes, beforeonGeneratedHook Design:
onWatchedandonCleanupas Complementary HooksonWatchedandonCleanupserve complementary, non-overlapping roles:onWatchedis a setup hook — runs after the dev server starts, provides thewatchersarray (plugins push watchers that get auto-closed on restart) and therestartfunction. It is not deprecated.onCleanupis a teardown hook — runs at lifecycle transition points with astageparameter for targeted cleanup.app.restartDevServer()is available onDevAppas a universal convenience method, usable from any hook.Dev Server Leak Audit
A full audit was performed across
core,bundlerutils,bundler-vite, andbundler-webpack(127 files). ThewriteTempcache Map inresolveAppWriteTemp.tswas the only leak identified — now fixed via thecleanup()method. All other mutable state is properly scoped per-app-instance through factory functions and gets garbage collected when the old app goes out of scope on restart.✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.