Working on a browser game with Phaser 3 and Vite and kept running into the same issue: edit a file, Vite hot-reloads the module, and the canvas either goes blank or the game enters some half-baked state where new code is running but old objects are still alive. Occasionally I'd get two canvas elements stacked in the DOM. Not a great iteration loop.
The root problem is that Phaser creates and owns a canvas element, and when modules hot-reload, the old Phaser instance doesn't automatically clean up. Orphaned instances, event listeners pointing at dead objects, the works.
Fix: explicit teardown in the HMR accept handler using import.meta.hot:
// main.js
import Phaser from 'phaser';
import { GameConfig } from './config';
let game = new Phaser.Game(GameConfig);
if (import.meta.hot) {
import.meta.hot.accept('./config', (newModule) => {
game.destroy(true);
game = new Phaser.Game(newModule.GameConfig);
});
import.meta.hot.dispose(() => {
game.destroy(true);
});
}This works well for config changes and anything that flows through your GameConfig. For changes deeper in scene logic you still need a full reload. There's no clean way to hot-swap a running Phaser Scene mid-state that I've found.
One gotcha: game.destroy(true) removes the canvas from the DOM entirely. If you're mounting into a specific container element, the new instance needs to re-append to it. Passing the container ID in your GameConfig handles this automatically, but if you're doing anything custom with your mount point, watch for a second canvas appearing outside the container on reload.
Anyone found a way to preserve actual scene state across reloads? Full teardown/reinit is fine for structural changes, but it'd be nice to not lose player position and score every time I tweak a balance constant.