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
4 changes: 2 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"language": "en,en-gb",
"words": [
"memfs",
"colorette",
"noextension",
"fullhash",
"execa",
Expand All @@ -24,7 +23,8 @@
"cachable",
"finalhandler",
"hono",
"rspack"
"rspack",
"malformed"
],
"ignorePaths": [
"CHANGELOG.md",
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
16 changes: 7 additions & 9 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ jobs:
group: lint-${{ matrix.os }}-v${{ matrix.node-version }}-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
Expand All @@ -53,9 +53,6 @@ jobs:
- name: Check types
run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi

- name: Security audit
run: npm run security

- name: Validate PR commits with commitlint
if: github.event_name == 'pull_request'
run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose
Expand All @@ -64,9 +61,10 @@ jobs:
name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18.x, 20.x, 22.x, 24.x]
node-version: [20.x, 22.x, 24.x, 25.x]
webpack-version: [latest]

runs-on: ${{ matrix.os }}
Expand All @@ -76,10 +74,10 @@ jobs:
cancel-in-progress: true

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
Expand All @@ -91,6 +89,6 @@ jobs:
run: npm run test:coverage -- --ci

- name: Submit coverage data to codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
114 changes: 111 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ See [below](#other-servers) for an example of use with fastify.
| **[`etag`](#tag)** | `boolean\| "weak"\| "strong"` | `undefined` | Enable or disable etag generation. |
| **[`lastModified`](#lastmodified)** | `boolean` | `undefined` | Enable or disable `Last-Modified` header. Uses the file system's last modified value. |
| **[`cacheControl`](#cachecontrol)** | `boolean\|number\|string\|Object` | `undefined` | Enable or disable setting `Cache-Control` response header. |
| **[`cacheImmutable`](#cacheimmutable)** | `boolean\` | `undefined` | Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets. |
| **[`cacheImmutable`](#cacheimmutable)** | `boolean` | `undefined` | Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets. |
| **[`publicPath`](#publicpath)** | `string` | `undefined` | The public path that the middleware is bound to. |
| **[`stats`](#stats)** | `boolean\|string\|Object` | `stats` (from a configuration) | Stats options object or preset name. |
| **[`serverSideRender`](#serversiderender)** | `boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. |
| **[`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. |
| **[`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 @@ -459,12 +460,104 @@ const app = new express();
app.use(instance);

instance.waitUntilValid(() => {
const filename = instance.getFilenameFromUrl("/bundle.js");
let resolver;

try {
resolved = instance.getFilenameFromUrl("/bundle.js");
} catch (err) {
console.log(`Error: ${err}`);
}

if (!resolved) {
console.log("Not found");
return;
}

console.log(`Filename is ${filename}`);
});
```

### `plugin(compiler, options)`

Creates middleware instance in plugin mode.

In plugin mode, stats output is written through custom code (i.e. in callback for `watch` or where you are calling `stats.toString(options)`) instead of `console.log`.
In this case, the `stats` option is not supported because `webpack-dev-middleware` does not have access to the code where the stats will be output.
You will also need to manually run the `watch` method.

Why do you need this mode? In some cases, you may want to have multiple dev servers or run only one dev server when you have multiple configurations, and this is suitable for you.

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

const compiler = webpack({
plugins: [
{
apply(compiler) {
const devMiddleware = middleware(
compiler,
{
/* webpack-dev-middleware options */
},
true,
);
},
},
],
/* Webpack configuration */
});

compiler.watch((err, stats) => {
if (err) {
console.error(err);
return;
}

console.log(stats.toString());
});
```

### Plugin wrappers

The following wrappers enable plugin mode for framework integrations:

- `middleware(compiler, options, true)` (connect/express like middleware)
- `middleware.koaWrapper(compiler, options, true)`
- `middleware.hapiWrapper(true)`
- `middleware.honoWrapper(compiler, options, true)`

They are equivalent to `koaWrapper`/`hapiWrapper`/`honoWrapper`, but use plugin mode logging behavior.

### `forwardError`

Type: `boolean`
Default: `false`

Enable or disable forwarding errors to the next middleware. If `true`, errors will be forwarded to the next middleware, otherwise, they will be handled by `webpack-dev-middleware` and a response will be handled case by case.

This option don't work with hono, koa and hapi, because of the differences in error handling between these frameworks and express.

```js
const express = require("express");
const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");

const compiler = webpack({
/* Webpack configuration */
});

const instance = middleware(compiler, { forwardError: true });

const app = express();
app.use(instance);

app.use((err, req, res, next) => {
console.log(`Error: ${err}`);
res.status(500).send("Something broke!");
});
```

## FAQ

### Avoid blocking requests to non-webpack resources.
Expand Down Expand Up @@ -681,6 +774,8 @@ const devMiddlewareOptions = {
const app = new Koa();

app.use(middleware.koaWrapper(compiler, devMiddlewareOptions));
// Alternative usage (when you want to use as a plugin, i.e. all stats will be printed by other code):
// app.use(middleware.koaWrapper(compiler, devMiddlewareOptions, true));

app.listen(3000);
```
Expand All @@ -699,14 +794,24 @@ const devMiddlewareOptions = {};
const server = Hapi.server({ port: 3000, host: "localhost" });

await server.register({
plugin: devMiddleware.hapiPlugin(),
plugin: devMiddleware.hapiWrapper(),
options: {
// The `compiler` option is required
compiler,
...devMiddlewareOptions,
},
});

// Alternative usage (when you want to use as a plugin, i.e. all stats will be printed by other code):
// await server.register({
// plugin: devMiddleware.hapiWrapper(true),
// options: {
// // The `compiler` option is required
// compiler,
// ...devMiddlewareOptions,
// },
// });

await server.start();

console.log("Server running on %s", server.info.uri);
Expand Down Expand Up @@ -755,6 +860,9 @@ const app = new Hono();

app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions));

// Alternative usage (when you want to use as a plugin, i.e. all stats will be printed by other code):
// const honoDevMiddleware = devMiddleware.honoWrapper(compiler, devMiddlewareOptions, true)

serve(app);
```

Expand Down
Loading
Loading