From 9c50941e64d036feeb08b9ac665944a0922f035e Mon Sep 17 00:00:00 2001 From: Mehdi Date: Tue, 20 Oct 2026 10:48:11 -0400 Subject: [PATCH] fix(types): expose middleware on koa and hono wrappers --- .changeset/typed-wrapper-middleware.md | 5 ++++ src/index.js | 4 +-- test/fixtures/types/wrappers.cts | 38 ++++++++++++++++++++++++++ test/wrapper-types.test.js | 24 ++++++++++++++++ types/index.d.ts | 12 +++++--- 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 .changeset/typed-wrapper-middleware.md create mode 100644 test/fixtures/types/wrappers.cts create mode 100644 test/wrapper-types.test.js diff --git a/.changeset/typed-wrapper-middleware.md b/.changeset/typed-wrapper-middleware.md new file mode 100644 index 000000000..ab72378cf --- /dev/null +++ b/.changeset/typed-wrapper-middleware.md @@ -0,0 +1,5 @@ +--- +"webpack-dev-middleware": patch +--- + +Expose the existing `devMiddleware` property in the Koa and Hono wrapper return types, preserving custom request and response types. diff --git a/src/index.js b/src/index.js index 8eb126034..a2980a553 100644 --- a/src/index.js +++ b/src/index.js @@ -1006,7 +1006,7 @@ wdm.hapiWrapper = hapiWrapper; * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options * @param {boolean=} usePlugin whether to use as webpack plugin - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper + * @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void) & { devMiddleware: API }} koa wrapper */ function koaWrapper(compiler, options = {}, usePlugin = false) { const devMiddleware = wdm(compiler, options, usePlugin); @@ -1149,7 +1149,7 @@ wdm.koaWrapper = koaWrapper; * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono wrapper + * @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void) & { devMiddleware: API }} hono wrapper */ function honoWrapper(compiler, options = {}, usePlugin = false) { const devMiddleware = wdm(compiler, options, usePlugin); diff --git a/test/fixtures/types/wrappers.cts b/test/fixtures/types/wrappers.cts new file mode 100644 index 000000000..8b1546702 --- /dev/null +++ b/test/fixtures/types/wrappers.cts @@ -0,0 +1,38 @@ +import { IncomingMessage, ServerResponse } from "node:http"; +import webpack = require("webpack"); +import middleware = require("../../.."); + +const compiler = webpack({ mode: "development" }); +const koa = middleware.koaWrapper(compiler); +const hono = middleware.honoWrapper(compiler); + +for (const wrapper of [koa, hono]) { + const api: ReturnType = wrapper.devMiddleware; + api.waitUntilValid(() => {}); + api.invalidate(() => {}); + api.close(() => {}); + api.context.options.publicPath = "/assets/"; + // @ts-expect-error The underlying API must not become untyped. + wrapper.devMiddleware.missingMethod(); + // @ts-expect-error Invalid options must still be rejected. + wrapper.devMiddleware.context.options.publicPath = 123; +} + +interface CustomRequest extends IncomingMessage { + customRequest: string; +} +interface CustomResponse extends ServerResponse { + customResponse: string; +} + +const customKoa = middleware.koaWrapper(compiler); +const customHono = middleware.honoWrapper(compiler); +for (const wrapper of [customKoa, customHono]) { + const api: middleware.API = wrapper.devMiddleware; + wrapper.devMiddleware.context.options.headers = (req, res) => ({ + "X-Request": req.customRequest, + "X-Response": res.customResponse, + }); + // @ts-expect-error Custom request properties remain typed. + wrapper.devMiddleware.context.options.headers = (req) => ({ "X-Invalid": req.missingProperty }); +} diff --git a/test/wrapper-types.test.js b/test/wrapper-types.test.js new file mode 100644 index 000000000..80592e7ad --- /dev/null +++ b/test/wrapper-types.test.js @@ -0,0 +1,24 @@ +import path from "node:path"; +import ts from "typescript"; + +describe("wrapper types", () => { + it("exposes each wrapper's middleware API and preserves request/response types", () => { + const program = ts.createProgram( + [path.resolve(__dirname, "fixtures/types/wrappers.cts")], + { + strict: true, + noEmit: true, + skipLibCheck: true, + module: ts.ModuleKind.NodeNext, + moduleResolution: ts.ModuleResolutionKind.NodeNext, + types: ["node"], + }, + ); + const diagnostics = ts.getPreEmitDiagnostics(program); + expect( + diagnostics.map((diagnostic) => + ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), + ), + ).toEqual([]); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index d2ed271a2..84801011f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -105,7 +105,7 @@ declare function hapiWrapper< * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options * @param {boolean=} usePlugin whether to use as webpack plugin - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper + * @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void) & { devMiddleware: API }} koa wrapper */ declare function koaWrapper< RequestInternal extends IncomingMessage = import("node:http").IncomingMessage, @@ -114,14 +114,16 @@ declare function koaWrapper< compiler: Compiler | MultiCompiler, options?: Options | undefined, usePlugin?: boolean | undefined, -): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; +): ((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void) & { + devMiddleware: API; +}; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono wrapper + * @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void) & { devMiddleware: API }} hono wrapper */ declare function honoWrapper< RequestInternal extends IncomingMessage = import("node:http").IncomingMessage, @@ -130,7 +132,9 @@ declare function honoWrapper< compiler: Compiler | MultiCompiler, options?: Options | undefined, usePlugin?: boolean | undefined, -): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; +): ((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void) & { + devMiddleware: API; +}; type Schema = import("schema-utils/declarations/validate").Schema; type Compiler = import("webpack").Compiler; type MultiCompiler = import("webpack").MultiCompiler;