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/typed-wrapper-middleware.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ wdm.hapiWrapper = hapiWrapper;
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @param {boolean=} usePlugin whether to use as webpack plugin
* @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} kow wrapper
* @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void) & { devMiddleware: API<RequestInternal, ResponseInternal> }} koa wrapper
*/
function koaWrapper(compiler, options = {}, usePlugin = false) {
const devMiddleware = wdm(compiler, options, usePlugin);
Expand Down Expand Up @@ -1149,7 +1149,7 @@ wdm.koaWrapper = koaWrapper;
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @param {boolean=} usePlugin true when need to use as a plugin, otherwise false
* @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} hono wrapper
* @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void) & { devMiddleware: API<RequestInternal, ResponseInternal> }} hono wrapper
*/
function honoWrapper(compiler, options = {}, usePlugin = false) {
const devMiddleware = wdm(compiler, options, usePlugin);
Expand Down
38 changes: 38 additions & 0 deletions test/fixtures/types/wrappers.cts
Original file line number Diff line number Diff line change
@@ -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<typeof middleware> = 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<CustomRequest, CustomResponse>(compiler);
const customHono = middleware.honoWrapper<CustomRequest, CustomResponse>(compiler);
for (const wrapper of [customKoa, customHono]) {
const api: middleware.API<CustomRequest, CustomResponse> = 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 });
}
24 changes: 24 additions & 0 deletions test/wrapper-types.test.js
Original file line number Diff line number Diff line change
@@ -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([]);
});
});
12 changes: 8 additions & 4 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ declare function hapiWrapper<
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @param {boolean=} usePlugin whether to use as webpack plugin
* @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} kow wrapper
* @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void) & { devMiddleware: API<RequestInternal, ResponseInternal> }} koa wrapper
*/
declare function koaWrapper<
RequestInternal extends IncomingMessage = import("node:http").IncomingMessage,
Expand All @@ -114,14 +114,16 @@ declare function koaWrapper<
compiler: Compiler | MultiCompiler,
options?: Options<RequestInternal, ResponseInternal> | undefined,
usePlugin?: boolean | undefined,
): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void;
): ((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void) & {
devMiddleware: API<RequestInternal, ResponseInternal>;
};
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @param {boolean=} usePlugin true when need to use as a plugin, otherwise false
* @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} hono wrapper
* @returns {((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void) & { devMiddleware: API<RequestInternal, ResponseInternal> }} hono wrapper
*/
declare function honoWrapper<
RequestInternal extends IncomingMessage = import("node:http").IncomingMessage,
Expand All @@ -130,7 +132,9 @@ declare function honoWrapper<
compiler: Compiler | MultiCompiler,
options?: Options<RequestInternal, ResponseInternal> | undefined,
usePlugin?: boolean | undefined,
): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void;
): ((ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void) & {
devMiddleware: API<RequestInternal, ResponseInternal>;
};
type Schema = import("schema-utils/declarations/validate").Schema;
type Compiler = import("webpack").Compiler;
type MultiCompiler = import("webpack").MultiCompiler;
Expand Down