Webpack hot module replacement

I am working on Webpack HMR.
Destroy the game instance on HMR callback and restart the game.
It works but resources in preload are reloaded every time.

Is there any options/trick prevent resources reload?

I don’t think you can prevent loading of resources if you’re destroying the game instance. But I’m interested, what’s the problem with resources loading everytime it’s reloaded. if it’s loading from disk on the same machine than it doesn’t take much time that would bother anyone.

Because the resources loading need 2-3 second.
I have tried to replace “game.cache” with the old CacheManager but not working. I will try to take a look Loader source code later and override it.

Maybe you can do something like removing & adding scene to game instance at runtime on HMR callback.

Destroying the game destroys the asset caches as well.

If you want to destroy the game but minimize reloading, add HTTP headers to allow browser caching.

An alternative to destroying the game is destroying all scenes and then “rebooting”.

3 Likes

Finally, I found a solution. This may also useful for SPA website

override.ts, helper function to override the prototype of Phaser.Loader.FileTypes.XXXFile

// eslint-disable-next-line @typescript-eslint/ban-types
export function overrideHelper() {
  const callbacks: (() => void)[] = [];

  function override<
    F extends { prototype: any },
    I extends F['prototype'],
    K extends keyof I,
    Fn extends (this: I, ...args: Parameters<I[K]>) => ReturnType<I[K]>
  >(classes: F, methodName: K, overrideFunction: (defaultFn: Fn) => Fn) {
    const defaultFn = classes.prototype[methodName];
    classes.prototype[methodName] = overrideFunction(defaultFn);

    callbacks.push(() => {
      classes.prototype[methodName] = defaultFn;
    });
  }

  return [override, () => callbacks.forEach(reset => reset())] as const;
}

The scripts to reuse assets

import { overrideHelper } from './override';

if (!window.phaserMemory) {
  window.phaserMemory = {};
}

const isPhaserFile = (file: any): file is typeof Phaser.Loader.File => {
  return (
    file &&
    file.prototype &&
    'load' in file.prototype &&
    'onProcess' in file.prototype &&
    'onProcessComplete' in file.prototype
  );
};

const files = Object.keys(Phaser.Loader.FileTypes);

const [override, resetOverride] = overrideHelper();

for (const fileType of files) {
  const file = Phaser.Loader.FileTypes[fileType];

  if (!isPhaserFile(file)) {
    continue;
  }

  override(file, 'onProcessComplete', function (onProcessComplete) {
    return function () {
      if (this.data) {
        window.phaserMemory[this.src] = this.data;
      }
      onProcessComplete.call(this);
    };
  });

  override(file, 'load', function (load) {
    return function () {
      const src = Phaser.Loader.GetURL(this, this.loader.baseURL);
      const hasData = window.phaserMemory[src];
      if (hasData) {
        this.src = src;
        setTimeout(() => {
          this.state = Phaser.Loader.FILE_LOADED;
          this.loader.nextFile(this, true);
        }, 0);
      } else {
        load.call(this);
      }
    };
  });

  override(file, 'onProcess', function (onProcess) {
    return function () {
      const data = window.phaserMemory[this.src];
      if (data) {
        this.data = data;
        Phaser.Loader.File.prototype.onProcess.call(this);
      } else {
        onProcess.call(this);
      }
    };
  });
}

if (module.hot) {
  module.hot.dispose(resetOverride);
}