-
-
Notifications
You must be signed in to change notification settings - Fork 298
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dev-server-hmr): initial implementation
- Loading branch information
Showing
21 changed files
with
917 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
# Dev Server >> Plugins >> Hot Module Replacement ||7 | ||
|
||
> **Warning:** this plugin is still experimental and may change until it | ||
> reaches a stable release. | ||
Plugin for introducing HMR (hot module replacement) support. | ||
|
||
Modules can be written to consume the provided HMR API at development | ||
time, allowing for them to update without reloading the page. | ||
|
||
## Installation | ||
|
||
Install the package: | ||
|
||
``` | ||
npm i --save-dev @web/dev-server-hmr | ||
``` | ||
|
||
Add the plugin to your `web-dev-server-config.mjs` or `web-test-runner.config.js`: | ||
|
||
```ts | ||
import { hmrPlugin } from '@web/dev-server-hmr'; | ||
|
||
export default { | ||
plugins: [hmrPlugin()], | ||
}; | ||
``` | ||
|
||
## Basic usage | ||
|
||
When the plugin is loaded, any HMR-compatible module will have the HMR API | ||
made available to it via `import.meta.hot`. | ||
|
||
By default, it will effectively do nothing until you have written code | ||
which consumes this API. | ||
|
||
For example, take the following module: | ||
|
||
```ts | ||
/** Adds two numbers */ | ||
export let add = (a, b) => { | ||
return a + b; | ||
}; | ||
``` | ||
|
||
In its current state, it will _not_ be HMR-compatible as it does not reference | ||
the HMR API. This means if our `add` module changes, the HMR plugin will | ||
trigger a full page reload. | ||
|
||
To make it compatible, we must use the HMR API via `import.meta.hot`: | ||
|
||
```ts | ||
/** Adds two numbers */ | ||
export let add = (a, b) => { | ||
return a + b; | ||
}; | ||
|
||
if (import.meta.hot) { | ||
import.meta.hot.accept(({ module }) => { | ||
add = module.add; | ||
}); | ||
} | ||
``` | ||
|
||
The plugin will detect that your module uses the HMR API and will make the | ||
`import.meta.hot` object available. | ||
|
||
Do note that in our example we wrapped this in an `if` statement. The reason | ||
for this is to account for if the plugin has not been loaded. | ||
|
||
## Note about production | ||
|
||
In production it is highly recommended you remove any of these HMR related | ||
blocks of code as they will effectively be dead code. | ||
|
||
## Use with libraries/frameworks | ||
|
||
This plugin exists primarily to serve as a base to other | ||
framework/ecosystem-specific implementations of HMR. | ||
|
||
It can be consumed directly as-is, but in future should usually be | ||
used via another higher level plugin layered on top of this. | ||
|
||
## API | ||
|
||
### `import.meta.hot.accept()` | ||
|
||
Calling `accept` without a callback will notify the plugin that your module | ||
accepts updates, but will not deal with the updates. | ||
|
||
This is only really useful if your module is one which has side-effects | ||
and does not need mutating on update (i.e. has no exports). | ||
|
||
Example: | ||
|
||
```ts | ||
// will be set each time the module updates as a side-effect | ||
window.someGlobal = 303; | ||
import.meta.hot.accept(); | ||
``` | ||
|
||
### `import.meta.hot.accept(({ module }) => { ... })` | ||
|
||
If you pass a callback to `accept`, it will be passed the updated module | ||
any time an update occurs. | ||
|
||
At this point, you should usually update any exports to be those on the | ||
new module. | ||
|
||
Example: | ||
|
||
```ts | ||
export let foo = 808; | ||
import.meta.hot.accept(({ module }) => { | ||
foo = module.foo; | ||
}); | ||
``` | ||
|
||
### `import.meta.hot.accept(['./dep1.js', './dep2.js'], ({ deps, module }) => { ... })` | ||
|
||
If you specify a list of dependencies as well as a callback, your callback | ||
will be provided with the up-to-date version of each of those modules. | ||
|
||
This can be useful if your updates require access to dependencies of the | ||
current module. | ||
|
||
Example: | ||
|
||
```ts | ||
import { add } from './add.js'; | ||
|
||
export let foo = add(10, 10); | ||
|
||
import.meta.hot.accept(['./add.js'], ({ deps, module }) => { | ||
foo = deps[0].add(10, 10); | ||
}); | ||
``` | ||
|
||
### `import.meta.hot.invalidate()` | ||
|
||
Immediately invalidates the current module which will then lead to reloading | ||
the page. | ||
|
||
Example: | ||
|
||
```ts | ||
export let foo = 303; | ||
|
||
import.meta.hot.accept(({ module }) => { | ||
if (!module.foo) { | ||
import.meta.hot.invalidate(); | ||
} else { | ||
foo = module.foo; | ||
} | ||
}); | ||
``` | ||
|
||
### `import.meta.hot.decline()` | ||
|
||
Notifies the server that you do not support updates, meaning any updates | ||
will result in a full page reload. | ||
|
||
This may be useful when your module makes global changes (side effects) which | ||
cannot be re-done. | ||
|
||
Example: | ||
|
||
```ts | ||
doSomethingGlobal(); | ||
|
||
import.meta.hot.decline(); | ||
``` | ||
|
||
### `import.meta.dispose(() => { ... })` | ||
|
||
Specifies a callback to be called when the current module is disposed of, | ||
before the new module is loaded and passed to `accept()`. | ||
|
||
Example: | ||
|
||
```ts | ||
export let foo = new Server(); | ||
|
||
foo.start(); | ||
|
||
import.meta.hot.accept(({ module }) => { | ||
foo = module.foo; | ||
foo.start(); | ||
}); | ||
|
||
import.meta.dispose(() => { | ||
foo.stop(); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Dev server HMR | ||
|
||
Plugin for using HMR (hot module reload) in the dev server. | ||
|
||
See [our website](https://modern-web.dev/docs/dev-server/plugins/hmr/) for full documentation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
const renderedComponents = new Set(); | ||
|
||
export class MyComponent extends HTMLElement { | ||
static styles = ` | ||
h1 { color: hotpink; } | ||
p { color: olivegreen; } | ||
`; | ||
|
||
static template = ` | ||
<h1>Foo</h1> | ||
<p>Bar</p> | ||
`; | ||
|
||
constructor() { | ||
super(); | ||
this.attachShadow({ mode: 'open' }); | ||
} | ||
|
||
connectedCallback() { | ||
renderedComponents.add(this); | ||
this.render(); | ||
} | ||
|
||
disconnectedCallback() { | ||
renderedComponents.delete(this); | ||
} | ||
|
||
render() { | ||
this.shadowRoot.innerHTML = ` | ||
<style>${MyComponent.styles}</style> | ||
${MyComponent.template} | ||
`; | ||
} | ||
} | ||
|
||
if (import.meta.hot) { | ||
import.meta.hot.accept(({ module }) => { | ||
MyComponent.styles = module.MyComponent.styles; | ||
MyComponent.template = module.MyComponent.template; | ||
for (const component of renderedComponents) { | ||
component.render(); | ||
} | ||
}); | ||
} | ||
|
||
if (!window.customElements.get('my-component')) { | ||
window.customElements.define('my-component', MyComponent); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<html> | ||
<head></head> | ||
|
||
<body> | ||
<my-component></my-component> | ||
<script type="module" src="./component.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const { hmrPlugin } = require('../dist/index'); | ||
|
||
module.exports = { | ||
rootDir: '.', | ||
plugins: [hmrPlugin()], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// this file is autogenerated with the generate-mjs-dts-entrypoints script | ||
export * from './dist/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// this file is autogenerated with the generate-mjs-dts-entrypoints script | ||
import cjsEntrypoint from './dist/index.js'; | ||
|
||
const { hmrPlugin } = cjsEntrypoint; | ||
|
||
export { hmrPlugin }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"name": "@web/dev-server-hmr", | ||
"version": "0.0.1", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"description": "Plugin for using HMR in @web/dev-server", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/modernweb-dev/web.git", | ||
"directory": "packages/dev-server-hmr" | ||
}, | ||
"author": "modern-web", | ||
"homepage": "https://github.com/modernweb-dev/web/tree/master/packages/dev-server-hmr", | ||
"main": "dist/index.js", | ||
"engines": { | ||
"node": ">=10.0.0" | ||
}, | ||
"scripts": { | ||
"build": "tsc", | ||
"start:demo": "web-dev-server --config demo/server.config.js", | ||
"test": "mocha \"test/**/*.test.ts\" --require ts-node/register --reporter dot", | ||
"test:watch": "mocha \"test/**/*.test.ts\" --require ts-node/register --watch --watch-files src,test --reporter dot" | ||
}, | ||
"files": [ | ||
"*.d.ts", | ||
"*.js", | ||
"*.mjs", | ||
"dist", | ||
"src" | ||
], | ||
"dependencies": { | ||
"@web/dev-server-core": "^0.2.2" | ||
}, | ||
"devDependencies": {}, | ||
"exports": { | ||
".": { | ||
"import": "./index.mjs", | ||
"require": "./dist/index.js" | ||
} | ||
} | ||
} |
Oops, something went wrong.