Comparing version 1.4.1 to 1.5.0-alpha.1
57
index.js
@@ -30,2 +30,4 @@ import process from 'process' | ||
let messagePort | ||
const watcher = chokidar | ||
@@ -42,2 +44,4 @@ .watch([]) | ||
log('Invalidating %s', Array.from(invalidatedFiles).join(', ')) | ||
messagePort.postMessage(realFilePath) | ||
}) | ||
@@ -81,3 +85,3 @@ .on('unlink', (relativeFilePath) => { | ||
export function load(url, context, defaultLoad) { | ||
export async function load(url, context, defaultLoad) { | ||
const parsedUrl = new URL(url) | ||
@@ -89,3 +93,52 @@ | ||
return defaultLoad(url, context, defaultLoad) | ||
const result = await defaultLoad(url, context, defaultLoad) | ||
if (result.format === 'module') { | ||
const source = result.source.toString() | ||
const newSource = source.replace( | ||
/if\s*\(\s*import\.meta\.hot\s*\)/, | ||
'if (globalThis.hotEsm.extendImportMeta(import.meta, () => import(import.meta.url)), import.meta.hot.accept)', | ||
) | ||
return { | ||
...result, | ||
source: newSource, | ||
} | ||
} | ||
return result | ||
} | ||
export function globalPreload({port}) { | ||
messagePort = port | ||
return ` | ||
globalThis.hotEsm = { | ||
extendImportMeta(importMeta, importModule) { | ||
const filePath = new URL(importMeta.url).pathname | ||
const handlers = this.handlers | ||
importMeta.hot = { | ||
accept(callback) { | ||
if (!handlers[filePath]) { | ||
handlers[filePath] = async () => { | ||
callback(await importModule()) | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
handlers: {} | ||
} | ||
port.onmessage = (event) => { | ||
const path = event.data | ||
const handlers = globalThis.hotEsm.handlers | ||
if (handlers[path]) { | ||
handlers[path]() | ||
} | ||
} | ||
` | ||
} |
{ | ||
"name": "hot-esm", | ||
"version": "1.4.1", | ||
"version": "1.5.0-alpha.1", | ||
"description": "ESM hot-reloading for Node.js", | ||
@@ -49,4 +49,5 @@ "keywords": [ | ||
"verbose": true, | ||
"timeout": "5m" | ||
"timeout": "5m", | ||
"concurrency": 1 | ||
} | ||
} |
@@ -16,2 +16,75 @@ # hot-esm | ||
## In Development: Hot Module Replacement | ||
```js | ||
export let text = 'Hello World!' | ||
if (import.meta.hot) { | ||
import.meta.hot.accept((module) => { | ||
text = module.text | ||
}) | ||
} | ||
``` | ||
The interface that hot-esm currently provides is pretty clunky: | ||
- It requires manual re-importing of modules without knowledge about updates | ||
- It requires manual cleanup of side-effects when a new version of a module is | ||
imported | ||
To address these problems, I'm experimenting with implementing an HMR interface | ||
similar to those provided by bundlers. Namely, I'm taking inspiration from the | ||
[HMR API](https://vitejs.dev/guide/api-hmr.html) provided by Vite. | ||
### Implementation | ||
Using the | ||
[`globalPreload()`](https://nodejs.org/docs/latest-v17.x/api/esm.html#globalpreload) | ||
loader hook, hot-esm creates a [global registry](/index.js#L128) to track | ||
modules that can accept updates. | ||
For each module that subscribes using `import.meta.hot.accept()`, hot-esm | ||
tracks: | ||
- The callback provided as argument | ||
- The absolute path to the module, derived from `import.meta.url` | ||
- A function defined within the module to re-`import()` that module. This is | ||
necessary because the `globalPreload()` hook does not allow dynamic | ||
`import()`. | ||
In order to define `import.meta.hot.accept()` and allow it to collect the above | ||
information, the [module source code is modified](/index.js#L95) via the | ||
[`load()`](https://nodejs.org/docs/latest-v17.x/api/esm.html#loadurl-context-defaultload) | ||
loader hook. In order to minimize the disruption to stack trace line numbers, | ||
the first instance of | ||
```js | ||
if (import.meta.hot) { | ||
// Code | ||
} | ||
``` | ||
is, without changing the line count, replaced with: | ||
```js | ||
if (globalThis.hotEsm.extendImportMeta(import.meta, () => import(import.meta.url)), import.meta.hot) { | ||
// Code | ||
} | ||
``` | ||
[`globalThis.hotEsm.extendImportMeta()`](/index.js#L114-L127) then ensures that | ||
the needed information is added to the global registry when | ||
`import.meta.hot.accept()` is called. | ||
When it comes to delivering updated modules to subscribers, when a module is updated, | ||
[its file path is sent](/index.js#L44) [to the registry](/index.js#L131-L138). | ||
From there, the callback passed to `import.meta.hot.accept()` is called with the | ||
updated module. | ||
### Ongoing Work | ||
I plan to try this interface against a few usecases before: | ||
- Looking for a more robust way to modify the module source code | ||
- Potentially expanding the interface of `import.meta.hot.accept()` to allow | ||
modules to accept updates for direct dependencies. | ||
- Potentially implementing other methods from Vite's HMR API. | ||
## Usage | ||
@@ -18,0 +91,0 @@ hot-esm provides a |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24952
12
602
145
1
1