Skip to content
Open
Show file tree
Hide file tree
Changes from all 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.
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 on lines 10 to 13
},
],
],
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