Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/FredKSchott/esm-hmr
a Hot Module Replacement (HMR) API for your ESM-based dev server.
https://github.com/FredKSchott/esm-hmr
esm-hmr hmr-server-engine snowpack
Last synced: 8 days ago
JSON representation
a Hot Module Replacement (HMR) API for your ESM-based dev server.
- Host: GitHub
- URL: https://github.com/FredKSchott/esm-hmr
- Owner: FredKSchott
- License: mit
- Created: 2020-05-18T05:54:08.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-07-15T00:25:39.000Z (over 2 years ago)
- Last Synced: 2024-05-30T16:33:55.960Z (6 months ago)
- Topics: esm-hmr, hmr-server-engine, snowpack
- Language: TypeScript
- Homepage:
- Size: 52.7 KB
- Stars: 409
- Watchers: 9
- Forks: 12
- Open Issues: 13
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ESM Hot Module Replacement (ESM-HMR) Spec
**Authors:** [Fred K. Schott](https://github.com/FredKSchott) (Snowpack), [Jovi De Croock](https://github.com/JoviDeCroock) (Preact), [Evan You](https://github.com/yyx990803) (Vue)
**Status:** Archived, no longer under development.Hot Module Replacement (HMR) lets your browser live-update individual JavaScript modules in your application during development _without triggering a full browser reload or losing the current web application state._ This speeds up your development speed with faster updates on every change.
Web bundlers like Webpack, Rollup, and Parcel all implemented different, bundler-specific HMR interfaces. This makes it hard to share HMR integrations across dev environments. As a result, many framework integrations like React Fast Refresh and Preact's Prefresh need to be rewritten for every bundler that they'd like to support. See:
- https://github.com/facebook/react/issues/16604#issuecomment-528663101
- https://github.com/JoviDeCroock/prefresh**ESM-HMR is a standard HMR API for ESM-based dev environments.** The rise of bundle-free development creates the opportunity for a common, standard HMR API built on top of the browser's native module system. ESM-HMR is built for the browser's native module system, so it can be used in any ESM-based dev environment.
## Who's Using ESM-HMR?
- [Snowpack](http://snowpack.dev/)
## What's in This Repo?
1. `esm-hmr/client.js` - A client-side ESM-HMR runtime.
1. `esm-hmr/server.js` - A server-side ESM-HMR engine to manage connected clients.
1. An ESM-HMR spec to help your write your own client/server pieces. (coming soon)## Usage Example
```js
export let foo = 1;if (import.meta.hot) {
// Receive any updates from the dev server, and update accordingly.
import.meta.hot.accept(({ module }) => {
try {
foo = module.foo;
} catch (err) {
// If you have trouble accepting an update, mark it as invalid (reload the page).
import.meta.hot.invalidate();
}
});
// Optionally, clean up any side-effects in the module before loading a new copy.
import.meta.hot.dispose(() => {
/* ... */
});
}
```## ESM-HMR API Overview
All ESM-HMR implementations will follow this API the behavior outlined below. If you have any questions (or would like clarity on some undefined behavior) file an issue and we'll take a look!
### `import.meta.hot`
```js
if (import.meta.hot) {
// Your HMR code here...
}
```- If HMR is enabled, `import.meta.hot` will be defined.
- If HMR is disabled (ex: you are building for production), `import.meta.hot` should be undefined.
- You can expect your production build to strip out `if (import.meta.hot) { ... }` as dead code.
- **Important:** You must use the fully expanded `import.meta.hot` statement somewhere in the file so that the server can statically check and enable HMR usage.Note: [`import.meta`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import.meta) is the new location for module metadata in ES Modules.
### `import.meta.hot.accept`
#### `accept()`
```js
import.meta.hot.accept();
```- Accept HMR updates for this module.
- When this module is updated, it will be automatically re-imported by the browser.
- **Important:** Re-importing an updated module instance doesn't automatically replace the current module instance in your application. If you need to update your current module's exports, you'll need a callback handler.**USE CASE:** Your module has no exports, and runs just by being imported (ex: adds a `` element to the page).
#### `accept(handler: ({module: any}) => void)`
```js
export let foo = 1;
import.meta.hot.accept(
({
module, // An imported instance of the new module
}) => {
foo = module.foo;
}
);
```- Accept HMR updates for this module.
- Runs the accept handler with the updated module instance.
- Use this to apply the new module exports to the current application's module instance. This is what accepts your update into the the running application.**This is an important distinction!** ESM-HMR never replaces the accepting module for you. Instead, the current module is given an instance of the updated module in the `accept()` callback. It's up to the `accept()` callback to apply that update to the current module in the current application.
**USE CASE:** Your module has exports that need to be updated.
#### `accept(deps: string[], handler: ({deps: any[]; module: any;}) => void)`
```js
import moduleA from "./modules/a.js";
import moduleB from "./modules/b.js";export let store = createStore({ a: moduleA, b: moduleB });
import.meta.hot.accept(
["./modules/a.js", "./modules/b.js"],
({ module, deps }) => {
// Get the new
store.replaceModules({
a: deps[0].default,
b: deps[1].default,
});
}
);
```Sometimes, it's not possible to update an existing module without a reference to its dependencies. If you pass an array of dependency import specifiers to your accept handler, those modules will be available to the callback via the `deps` property. Otherwise, the `deps` property will be empty.
**USE CASE:** You need a way to reference your dependencies to update the current module.
### `dispose(callback: () => void)`
```js
document.head.appendChild(styleEl);
import.meta.hot.dispose(() => {
document.head.removeChild(styleEl);
});
```The `dispose()` callback executes before a new module is loaded and before `accept()` is called. Use this to remove any side-effects and perform any cleanup before loading a second (or third, or forth, or...) copy of your module.
**USE CASE:** Your module has side-effects that need to be cleaned up.
### `decline()`
```js
import.meta.hot.decline();
```- This module is not HMR-compatible.
- Decline any updates, forcing a full page reload.**USE CASE:** Your module cannot accept HMR updates, for example due to permenant side-effects.
### `invalidate()`
```js
import.meta.hot.accept(({ module }) => {
if (!module.foo) {
import.meta.hot.invalidate();
}
});
```- Conditionally invalidate the current module when called.
- This will reject an in-progress update and force a page reload.**USE CASE:** Conditionally reject an update if some condition is met.
### `import.meta.hot.data`
```js
export let foo = 1;if (import.meta.hot) {
// Recieve data from the dispose() handler
import.meta.hot.accept(({ module }) => {
foo = import.meta.hot.data.foo || module.foo;
});
// Pass data to the next accept handler call
import.meta.hot.dispose(() => {
import.meta.hot.data = { foo };
});
}
```- You can use `import.meta.hot.data` to pass data from the `dispose()` handler(s) to the `accept()` handler(s).
- Defaults to an empty object (`{}`) every time an update starts.<!--
## ESM-HMR Behavior Overview_Note: This spec is still in progress, and is more of a rough overview at this point._
### Terminology
- "HMR Server Engine" - The server component of HMR. Responsible for tracking changes and sending updates to the client runtime.
- "HMR Client Runtime" - The client/browser component of HMR. Responsible for receiving updates from the server engine and updating the client appropriately.
- "HMR-Enabled File" - Any file that includes a reference to `import.meta.hot` is considered HMR-Enabled.### Update Events
When a file is changed, 1 or more events are sent to the browser. What these events look like (and how they are is handled) depends on your application:
- If the changed file is HMR-Enabled, the server will send an update for that one file.
- Otherwise, the server will "bubble" the update event up to check each parent of that file.
- Event bubbling is repeated until every event is handled, or an event has reached
- If an event bubbles all the way up without finding an HMR-enabled parent, the event is considered "unhandled" and a full page reload is triggered.
-->## Prior Art
This spec wouldn't exist without the prior work of the following projects:
- @rixo's [rollup-plugin-hot](https://github.com/rixo/rollup-plugin-hot)
- [Webpack HMR](https://webpack.js.org/concepts/hot-module-replacement/)
- [Parcel HMR](https://parceljs.org/hmr.html)