Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/patreeceeo/hot_mod

HMR for ES Modules
https://github.com/patreeceeo/hot_mod

deno developer-tools esm esmodules hmr hot-module-replacement javascript rapid-development rapid-prototyping typescript

Last synced: about 2 months ago
JSON representation

HMR for ES Modules

Awesome Lists containing this project

README

        

# hot_mod

https://user-images.githubusercontent.com/578371/224897357-0119a373-5f50-4b46-b3de-5f158d7c8591.mp4

An ESM-HMR client and server, allowing for hot-reloading client-side (and maybe server-side? untested) ESModules, as described by https://github.com/FredKSchott/esm-hmr.

## Usage with Deno

These modules are published at https://deno.land/x/hot_mod

In each client-side module:

```javascript
import { useClient } from "hot_mod/dist/client/mod.js";

useClient(import.meta);

// The following is just my best idea so far of how to write hot-reloadable modules :)
export const hotExports = {
// Add more identifiers here
drawPlayers // example
}
if (import.meta.hot) {
import.meta.hot.accept([], ({ module }) => {
for(const key of Object.keys(hotExports) as Array) {
hotExports[key] = module.hotExports[key]
}
});
}
// In app code, write hotExports.drawPlayers() instead of drawPlayers()
```

`import.meta.hot` will available in development (well, as long as serving from localhost. I mean to support some kind of configuration or environment variables for deciding when HMR should be enabled.)

Then, in the dev server, import the HMR engine and wire it up:

```typescript
import { serve } from "http";
import { relative } from "path";
import { debounce } from "async";
import { EsmHmrEngine } from "hot_mod/src/server/mod.ts";

interface ModuleEventHandler {
(paths: IterableIterator): void;
}

let listenerCount = 0;
const modifiedModuleUrls = new Set();
async function addModuleEventHandler(
handler: ModuleEventHandler,
absPaths: Array,
) {
const watcher = Deno.watchFs(absPaths, { recursive: true });

const debouncedListener = debounce(() => {
const copy = new Set(modifiedModuleUrls);
handler(copy.values());
modifiedModuleUrls.clear();
}, 200);

listenerCount++;
console.log(
`Module event handler #${listenerCount} for ${absPaths.join(", ")}`,
);

for await (const event of watcher) {
if (event.kind === "modify") {
for (const path of event.paths) {
// These strings must correspond to those created on the client:
// The pathname to the full URL to the module subject to hot reloading,
// e.g. new URL(import.meta.url).pathname;
const moduleId = "/" + relative(Deno.cwd(), path);
modifiedModuleUrls.add(moduleId);
}
}
if (modifiedModuleUrls.size > 0) {
debouncedListener();
}
}
}

const engine = new EsmHmrEngine((emitModuleModifiedEvent) => {
addModuleEventHandler((paths) => {
for (const path of paths) {
emitModuleModifiedEvent(path);
}
}, [Deno.cwd() + "/public"]);
});
serve((request) => {
const { socket, response } = Deno.upgradeWebSocket(request);
engine.addClient(socket);
return response;
}, { port: 12321 });
```

And run it with:

```sh
deno run --allow-net --allow-read --watch "path/to/dev_server.ts"
```

## API

See https://github.com/FredKSchott/esm-hmr

## Contributing

GitHub issues and pull requests welcome!