npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@olefjaerestad/hmr

v0.0.10

Published

Hot Module Reloading server and client side scripts.

Readme

hmr

Hot Module Reloading server and client side scripts. All you need to get up and running with HMR in your project.

Hot module reload (automatically reload code without reloading page) for:

  • CSS
  • Images

Hot reloading (automatic full page reload) for:

  • HTML
  • JS, with experimental HMR support for JS modules (script[type="module"]).

Requirements

Browser and Node environments supporting the following:

How to use

npm i @olefjaerestad/hmr

Create a hmr-server.js and add the following. Feel free to tweak the Server parameters as you see fit:

import { Server } from '@olefjaerestad/hmr';
import { join } from 'path';
import { fileURLToPath } from 'url';

new Server({
  hostname: 'localhost',
  port: 9001,
  watch: {
    paths: [
      join(fileURLToPath(import.meta.url), '../../dist'),
      join(fileURLToPath(import.meta.url), '../../../somefolder'),
      join(fileURLToPath(import.meta.url), '/../anotherfolder'),
    ]
  }
});

Run node hmr-server.js. This will start watching the given paths recursively for file changes. The paths don't have to exist at the time the script starts running, which might be useful in some dev setups. A good idea would be to use something like Concurrently and run this script in parallel with your other dev script(s).

Note: This package is an ES module, and as such requires either setting "type": "module" in your package.json or using an .mjs extension hmr-server.mjs.

Add the following somewhere in your client side code (e.g. create a hmr-client.js and include it in your markup):

/**
 * Note: if not using a bundler or similar, the import path must point to node_modules, e.g.
 * '../node_modules/@olefjaerestad/hmr/build/client.js'.
 */
import { Client } from '@olefjaerestad/hmr/build/client.js';

new Client({
  hostname: 'localhost',
  port: 9001,
  onMessageCallback: (e, client) => {
    console.log('Client.onMessageCallback()');
    console.log(e);
    client.replaceNodesByFilename({filename: e.filename});
  },
  onOpenCallback: (e) => console.log(e),
  onCloseCallback: (e) => console.log(e),
  onErrorCallback: (e) => console.log(e),
});

This will connect your browser to the HMR Server, and you'll be notified when any of the watched files change.

Note, if you're making a separate file with this content, make sure to mark it as a module when including it in your html (<script type="module">).

General idea

  • To be used while developing.
  • Keeps a websocket server running at all times.
  • The websocket server watches for file changes and notifies connected clients.
  • The clients decide themselves what they want to do when files change (or use defaults).
  • Expose a JS API.
  • No CLI API.

Docs

Server

new Server({
  hostname: 'localhost', // string. Required.
  port: 9001, // number. Required.
  watch: { // Required.
    ignoredFileExtensions, // string[]. Optional. Example: ['.d.ts', '.tsbuildinfo']
    notifyClientsOnFileChange: true, // boolean. Optional. Notify connected clients when a file changes. Default: true.
    paths: [ // string or string[]. Required.
      join(fileURLToPath(import.meta.url), '../../dist'),
      join(fileURLToPath(import.meta.url), '../../../somefolder'),
      join(fileURLToPath(import.meta.url), '/../anotherfolder'),
      join(fileURLToPath(import.meta.url), './../yetanotherfolder/file.js'),
    ],
    verbose: false, // boolean. Optional. Outputs file events to console. Useful for debugging. Default: false.
  }
});

Server.addEventListener(eventName: string: callback: (event) => any): boolean

Run a callback on certain events. Useful e.g. if you need the filename of the changed file server side. Returns whether the callback could be added or not (a given callback can only be added once pr. eventName).

const hmrServer = new Server({...args});
function changeCallback(event) {
  console.log(event);
};
hmrServer.addEventListener('change', changeCallback);

Supported events:

  • change: When a file change is detected.

Server.emit(eventName: string, event: {[key: string]: any}): boolean

Emit an event, which triggers callbacks registered with addEventListener(). Returns false if no callbacks have been registered, true otherwise. If callbacks have been registered, true will be returned after the callbacks have been run. This is used internally, but feel free to use it however you like.

const hmrServer = new Server({...args});
hmrServer.emit('change', {
  value: {
    foo: 'bar'
  },
});

Server.notifyConnectedClients(event: IFileChangedEvent): void

Notify connected clients about a file change.

Server.removeEventListener(eventName: string: callback: (event) => any): boolean

Remove a callback registered with addEventListener(). Returns whether the callback could be removed or not.

const hmrServer = new Server({...args});
function changeCallback(event) {
  console.log(event);
};
hmrServer.addEventListener('change', changeCallback);
hmrServer.removeEventListener('change', changeCallback);

Server._connectedSockets

{[id: number]: WebSocket}

Server._ignoredFileExtensions

string[]

Server._lastChangedFile

{filename?: string, timestamp?: number}

Server._notifyClientsOnFileChange

boolean

Server._server

WebSocket.Server

https://github.com/websockets/ws#simple-server

Server._verbose

boolean

Client

new Client({
  hostname: 'localhost', // Must match hostname of Server. Required.
  port: 9001, // Must match port of Server. Required.
  onMessageCallback: (e: IFileChangedEvent, client: Client) => { // Run callback on file changes. Optional. If not passed, fallbacks to a default (and hopefully reasonable) behaviour.
    // client refers to the newly created instance:
    client.replaceNodesByFilename({ 
      filename: e.filename,
      includeJs: client._doJsHmr,
      verbose: client._verbose,
    });
  },
  onOpenCallback: (e: Event, client: Client) => console.log(e), // Run callback on connection to Server. Optional.
  onCloseCallback: (e: CloseEvent, client: Client) => console.log(e), // Run callback on disconnection from Server. Optional.
  onErrorCallback: (e: Event, client: Client) => console.log(e), // Run callback on connection error. Optional.
  verbose: true, // Optional. Outputs connection/file events to console. Useful for debugging. Default: false.
  doJsHmr: true, // Do HMR instead of page refresh for changes to javascript modules. This is experimental and quite buggy. Use at your own risk. Default: false.
});

Client._defaultOnMessageCallback(e: IFileChangedEvent): void:

Used on file changes if you don't pass an onMessageCallback to the Client constructor.

(e: IFileChangedEvent) => void

Client._doJsHmr

boolean

Client._socket

WebSocket

Client._verbose

boolean

Client.replaceNodesByFilename({filename: string, includeJs?: boolean = false, verbose?: boolean = false}): number

Replace nodes which reference filename (e.g. CSS <link>s). Return number of replaced nodes.

Utility functions

notify

Server side function you can use to manually notify all connected clients. This was originally created to allow web servers to notify clients whenever it restarted.

import { notify } from '@olefjaerestad/hmr';

notify({
  hostname: 'localhost', // Must match hostname of Server. Required.
  port: 9001, // Must match port of Server. Required.
  event: { // IFileChangedEvent. Required.
    type: 'restart',
  }
});

Typescript

import {
  IChangeEvent,
  IFileChangedEvent,
  TChangeCallback,
  TEventName,
} from '@olefjaerestad/hmr/build/types/types';

IFileChangedEvent is explained below, see the source code for more details.

IFileChangedEvent

Event emitted when files are changed.

interface IFileChangedEvent {
  filename?: string;
  filepath?: string;
  type: 'add' | 'addDir' | 'change' | 'restart' | 'unlink' | 'unlinkDir';
}

Dev (contributing to the project)

Branch out from master.

npm i

npm run dev

Open localhost:9000 in your browser.

Make a change to a file in src to trigger HMR. Save (no need to make any change) a file in static to trigger HMR. Save (no need to make any change) scripts/dev-server.js to trigger HMR.

A good starting point for getting to know the project is to have a look at the following files:

  • src/client.ts
  • src/server.ts
  • scripts/dev-server.js
  • scripts/hmr-server-dev.js
  • static/hmr-client-dev.js
  • package.json#scripts
  • nodemon.*.json

When you're done, create a pull request from your branch to master.

While developing, you can use the string '__ROLLUP_REPLACE_WITH_EMPTY_STRING__'. This will be replaced with an empty string when building project. Handy for tree shaking (e.g. in if statements).

Build

npm i

npm run build

npm run start

Open localhost:9000/index-prod.html in your browser.

Todo

  • Add (unit) tests.

FAQ

Why opt-in HMR for JS?

When replacing scripts, if the replaced script contains addEventListener, that event listener will fire as many times as the script has been replaced. This happens because scripts stay in memory even after they've been removed from DOM. This could also lead to huge memory leaks. This would be a less than ideal default. By opting in to JS HMR, you risk experiencing issues like these.