Hot reloading with typescript and node

4.1k views Asked by At

A lot of articles exist out there explaining how to hook up nodemon or ts-node-dev to automatically restart your TypeScript server on file changes, but restarting the whole server is starting to get pretty slow on my large TypeScript project, where startup times can take many tens of seconds (thanks typeorm).

Frontend development has been dominated by boilerplates like create-react-app, nextjs and more that offer hot reloading without even a page refresh, resulting in very fast feedback times for frontend changes.

What's the equivalent for doing that on a Node API, especially one where the source files are in TypeScript, not JS? Should I be using tsc --watch and then invalidating require.cache as compiled output completes or something?

1

There are 1 answers

0
malix On

We use

/* eslint-env node */

import path from 'path';

import {
  disposeOnReload,
  enableHotReload,
  getReloadCount,
  hotClass,
  hotRequire,
  hotRequireExportedFn,
  registerUpdateReconciler
} from '@hediet/node-reload';
import { IStart, Logger } from './interfaces';
import { initNextJs } from './modules/application/initNextJs';
import { Globals } from './global';

const index = path.join(__dirname, '../client/index.html');

enableHotReload({ entryModule: module, loggingEnabled: false });
registerUpdateReconciler(module);

Logger.log('[ADMIN] Globals.init()');
Globals.init();

@hotClass(module)
class Loader {
  private v1 = 1; // for reload

  static start(info: IStart) {
    return new Promise<void>((done) =>
      hotRequire<typeof import('./server')>(module, './server', (server) => {
        Logger.log('[ADMIN] server.start()');
        server
          .start(info)
          .then(() => {
            done();
          })
          .catch((error) => {
            Logger.error('[ADMIN] Error starting!', { error });
            process.exit(1);
          });
        return {
          dispose: () => {
            Logger.log('[ADMIN] server.stop()');
            server.stop();
          }
        };
      })
    );
  }
}

let startInfo: IStart;
hotRequireExportedFn(module, Loader, { hasFnChanged: 'useSource' }, (loader) => {
  const reloadCount = getReloadCount(module);
  if (reloadCount === 0) {
    Logger.log('[ADMIN] initNextJs');
    const nextApp = initNextJs(true);
    console.time('************************************************** Prepared Next');
    const preparing = nextApp.prepare().then(() => {
      console.timeEnd('************************************************** Prepared Next');
    });
    startInfo = { nextApp, index, preparing };
  }
  setTimeout(() => loader.start(startInfo), 0);
  return { dispose: () => console.log('loader.dispose()') };
});```