Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hot-middleware-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack-dev-middleware": minor
---

Added a `hot` option that enables hot module replacement, replacing the need for `webpack-hot-middleware`. Pass `hot: true` to enable with defaults, or `hot: { path, heartbeat, log, statsOptions }` to customize. The client runtime is served by the middleware itself.
Comment thread
bjohansebas marked this conversation as resolved.
2 changes: 2 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ on:
branches:
- main
- next
- hot-middleware
pull_request:
branches:
- main
- next
- hot-middleware

permissions:
contents: read
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ logs
npm-debug.log*
.eslintcache
.cspellcache
/dist
/client
dist
/local
/reports
/test/outputs
Expand Down
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ See [below](#other-servers) for an example of use with fastify.
| **[`writeToDisk`](#writetodisk)** | `boolean\|Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. |
| **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. |
| **[`modifyResponseData`](#modifyresponsedata)** | `Function` | `undefined` | Allows to set up a callback to change the response data. |
| **[`hot`](#hot)** | `boolean\|Object` | `false` | Enables a Server-Sent Events endpoint that drives the browser HMR client. |
| **[`forwardError`](#forwarderror)** | `boolean` | `false` | Enable or disable forwarding errors to the next middleware. |

The middleware accepts an `options` Object. The following is a property reference for the Object.
Expand Down Expand Up @@ -312,6 +313,115 @@ middleware(compiler, {
});
```

### hot

Type: `Boolean | Object`
Default: `false`

Enables hot module replacement by serving a [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) endpoint that publishes the webpack compiler's `building`, `built` and `sync` events to connected clients. When `true`, defaults are used; pass an object to customise. Use this option together with the browser runtime shipped as `webpack-dev-middleware/client`.

```js
const webpack = require("webpack");

const compiler = webpack({
/* Webpack configuration with HotModuleReplacementPlugin and the client entry */
});

middleware(compiler, { hot: true });
```

#### `hot.path`

Type: `String`
Default: `'/__webpack_hmr'`

Path the SSE endpoint is served at. Must match the `path` option used by the client.

#### `hot.heartbeat`

Type: `Number`
Default: `10000`

Heartbeat interval (in milliseconds) used to keep the SSE connection alive when no compilation events are produced.

#### `hot.statsOptions`

Type: `Boolean | Object`
Default: `undefined`

Webpack stats options used when serializing compilation results for the SSE payload. Forwarded to `stats.toJson(...)`. By default only the minimal stats needed by the client are requested (`hash`, `timings`, `errors`, `warnings`) to avoid slowing down rebuilds. Pass `statsOptions: { modules: true }` if you want the module id → name map used for nicer client logging.

## Hot Module Replacement client

When the server is configured to serve the hot module replacement endpoint, the bundled application needs a small runtime that subscribes to that stream and applies the updates. `webpack-dev-middleware` ships that runtime under the `./client` subpath. Add it as a webpack entry next to your application code and enable `HotModuleReplacementPlugin`:

```js
const webpack = require("webpack");

module.exports = {
entry: ["webpack-dev-middleware/client", "./src/app.js"],
plugins: [new webpack.HotModuleReplacementPlugin()],
};
```

The runtime connects to `/__webpack_hmr` by default. Any of the options below can be set by adding a query string to the entry path:

```js
entry: [
"webpack-dev-middleware/client?reload=false&overlay=false",
"./src/app.js",
];
```

### Client options

| Name | Type | Default | Description |
| :-----------------: | :-------: | :--------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path` | `string` | `/__webpack_hmr` | Path the SSE endpoint is served at. Must match the server `hot.path`. |
| `timeout` | `number` | `20000` | Reconnection / heartbeat watchdog timeout in milliseconds. |
| `overlay` | `boolean` | `true` | Show compile-time errors in an in-page overlay. |
| `overlayWarnings` | `boolean` | `false` | Also show compile-time warnings in the overlay. |
| `overlayStyles` | `Object` | `{}` | JSON object of CSS overrides for the overlay container. Pass JSON-encoded value via query string. |
| `ansiColors` | `Object` | `{}` | JSON object overriding the ANSI → HTML color map used by the overlay. |
| `reload` | `boolean` | `true` | Fall back to a full page reload when an update cannot be applied through HMR (e.g. recovering from a broken build). Set to `false` to keep HMR-only. |
| `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. |
| `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). |
| `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. |
| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. |

### Programmatic API

`webpack-dev-middleware/client` also exports a few functions for advanced cases:

```js
const hotClient = require("webpack-dev-middleware/client");

// Receive every HMR payload (building / built / sync / custom).
hotClient.subscribeAll((payload) => {
console.log("hot event", payload);
});

// Receive payloads whose `action` is not recognised by the client (i.e. custom
// payloads published via the server's `instance.context.hot.publish(...)`).
hotClient.subscribe((payload) => {
// do something
});

// Replace the default error overlay with your own implementation.
hotClient.useCustomOverlay({
showProblems(type, lines) {
/* ... */
},
clear() {
/* ... */
},
});

// Connect manually when `autoConnect=false`. Accepts the same option keys as
// the query-string API above.
hotClient.setOptionsAndConnect({ path: "/__hmr" });
```

## API

`webpack-dev-middleware` also provides convenience methods that can be use to
Expand Down
22 changes: 18 additions & 4 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
const MIN_BABEL_VERSION = 7;

module.exports = (api) => {
api.assertVersion(MIN_BABEL_VERSION);
api.cache(true);

return {
presets: [
[
"@babel/preset-env",
{
modules: false,
targets: {
node: "20.9.0",
esmodules: true,
node: "0.12",
},
Comment thread
bjohansebas marked this conversation as resolved.
},
],
],
env: {
test: {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "18.12.0",
},
},
],
],
plugins: ["@babel/plugin-transform-runtime"],
},
},
};
};
28 changes: 28 additions & 0 deletions client-src/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable */

declare module "ansi-html-community" {
function ansiHtmlCommunity(str: string): string;
namespace ansiHtmlCommunity {
function setColors(colors: Record<string, string | string[]>): void;
}
export = ansiHtmlCommunity;
}

interface ClientReporter {
cleanProblemsCache(): void;
problems(
type: "errors" | "warnings",
obj: { errors: string[]; warnings: string[]; name?: string },
): boolean;
success(): void;
useCustomOverlay(customOverlay: unknown): void;
}

interface EventSourceWrapper {
addMessageListener(fn: (event: { data: string }) => void): void;
}

interface Window {
__wdmEventSourceWrapper?: Record<string, EventSourceWrapper>;
__webpack_dev_middleware_hot_reporter__?: ClientReporter;
}
Loading
Loading