From 65c100500335f449ff7505a1bdf47e78a02d993c Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 1 Mar 2026 05:19:19 +0000 Subject: [PATCH 01/15] feat: add support for plugin usage with dedicated wrappers and logging --- src/index.js | 105 ++++++++++++++++++++++++++++++++++++++-- src/utils/setupHooks.js | 9 +++- types/index.d.ts | 55 +++++++++++++++++++++ 3 files changed, 164 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 9e7cff79e..19b30a63b 100644 --- a/src/index.js +++ b/src/index.js @@ -90,6 +90,7 @@ const noop = () => {}; * @property {Watching | MultiWatching | undefined} watching watching * @property {Logger} logger logger * @property {OutputFileSystem} outputFileSystem output file system + * @property {boolean} isPlugin whether wdm is used as webpack plugin */ /** @@ -225,6 +226,7 @@ function wdm(compiler, options = {}) { options, compiler, logger: compiler.getInfrastructureLogger("webpack-dev-middleware"), + isPlugin: false, }; setupHooks(context); @@ -319,9 +321,10 @@ function wdm(compiler, options = {}) { /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal + * @param {boolean} usePlugin whether to use as webpack plugin * @returns {HapiPlugin} hapi wrapper */ -function hapiWrapper() { +function createHapiWrapper(usePlugin = false) { return { pkg: { name: "webpack-dev-middleware", @@ -337,6 +340,11 @@ function hapiWrapper() { const devMiddleware = wdm(compiler, rest); + if (usePlugin) { + // Use logger when used as webpack plugin + devMiddleware.context.isPlugin = true; + } + // @ts-expect-error if (!server.decorations.server.includes("webpackDevMiddleware")) { // @ts-expect-error @@ -387,18 +395,42 @@ function hapiWrapper() { }; } +/** + * @template HapiServer + * @template {HapiOptions} HapiOptionsInternal + * @returns {HapiPlugin} hapi wrapper + */ +function hapiWrapper() { + return createHapiWrapper(false); +} + +/** + * @template HapiServer + * @template {HapiOptions} HapiOptionsInternal + * @returns {HapiPlugin} hapi plugin wrapper + */ +function hapiPluginWrapper() { + return createHapiWrapper(true); +} + wdm.hapiWrapper = hapiWrapper; +wdm.hapiPluginWrapper = hapiPluginWrapper; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @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 */ -function koaWrapper(compiler, options) { +function createKoaWrapper(compiler, options, usePlugin = false) { const devMiddleware = wdm(compiler, options); + if (usePlugin) { + devMiddleware.context.isPlugin = true; + } + /** * @param {{ req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | { message: string }, state: object }} ctx context * @param {EXPECTED_FUNCTION} next next @@ -508,18 +540,46 @@ function koaWrapper(compiler, options) { return webpackDevMiddleware; } +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper + */ +function koaWrapper(compiler, options) { + return createKoaWrapper(compiler, options, false); +} + +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow plugin wrapper + */ +function koaPluginWrapper(compiler, options) { + return createKoaWrapper(compiler, options, true); +} + wdm.koaWrapper = koaWrapper; +wdm.koaPluginWrapper = koaPluginWrapper; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @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} hono wrapper */ -function honoWrapper(compiler, options) { +function createHonoWrapper(compiler, options, usePlugin = false) { const devMiddleware = wdm(compiler, options); + if (usePlugin) { + devMiddleware.context.isPlugin = true; + } + /** * @param {{ env: EXPECTED_ANY, body: EXPECTED_ANY, json: EXPECTED_ANY, status: EXPECTED_ANY, set: EXPECTED_ANY, req: RequestInternal & import("./utils/compatibleAPI").ExpectedIncomingMessage & { header: (name: string) => string }, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: EXPECTED_ANY, status: EXPECTED_ANY } }} context context * @param {EXPECTED_FUNCTION} next next function @@ -685,6 +745,45 @@ function honoWrapper(compiler, options) { return webpackDevMiddleware; } +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono wrapper + */ +function honoWrapper(compiler, options) { + return createHonoWrapper(compiler, options, false); +} + +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono plugin wrapper + */ +function honoPluginWrapper(compiler, options) { + return createHonoWrapper(compiler, options, true); +} + wdm.honoWrapper = honoWrapper; +wdm.honoPluginWrapper = honoPluginWrapper; + +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {API} webpack dev middleware + */ +function plugin(compiler, options = {}) { + const instance = wdm(compiler, options); + // Mark that wdm is used as webpack plugin (to use logger instead of console.log) + instance.context.isPlugin = true; + return instance; +} + +wdm.plugin = plugin; module.exports = wdm; diff --git a/src/utils/setupHooks.js b/src/utils/setupHooks.js index 50c55a0f8..abd88205c 100644 --- a/src/utils/setupHooks.js +++ b/src/utils/setupHooks.js @@ -138,8 +138,13 @@ function setupHooks(context) { // Avoid extra empty line when `stats: 'none'` if (printedStats) { - // eslint-disable-next-line no-console - console.log(printedStats); + if (context.isPlugin) { + // Use logger when used as webpack plugin + logger.log(printedStats); + } else { + // eslint-disable-next-line no-console + console.log(printedStats); + } } context.callbacks = []; diff --git a/types/index.d.ts b/types/index.d.ts index e3fb18505..d5cb9e54c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -165,8 +165,12 @@ declare function wdm< declare namespace wdm { export { hapiWrapper, + hapiPluginWrapper, koaWrapper, + koaPluginWrapper, honoWrapper, + honoPluginWrapper, + plugin, Schema, Compiler, MultiCompiler, @@ -231,6 +235,15 @@ declare function hapiWrapper< HapiServer, HapiOptionsInternal extends HapiOptions, >(): HapiPlugin; +/** + * @template HapiServer + * @template {HapiOptions} HapiOptionsInternal + * @returns {HapiPlugin} hapi plugin wrapper + */ +declare function hapiPluginWrapper< + HapiServer, + HapiOptionsInternal extends HapiOptions, +>(): HapiPlugin; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] @@ -245,6 +258,20 @@ declare function koaWrapper< compiler: Compiler | MultiCompiler, options?: Options | undefined, ): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow plugin wrapper + */ +declare function koaPluginWrapper< + RequestInternal extends IncomingMessage = import("http").IncomingMessage, + ResponseInternal extends ServerResponse = ServerResponse, +>( + compiler: Compiler | MultiCompiler, + options?: Options | undefined, +): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] @@ -259,6 +286,34 @@ declare function honoWrapper< compiler: Compiler | MultiCompiler, options?: Options | undefined, ): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono plugin wrapper + */ +declare function honoPluginWrapper< + RequestInternal extends IncomingMessage = import("http").IncomingMessage, + ResponseInternal extends ServerResponse = ServerResponse, +>( + compiler: Compiler | MultiCompiler, + options?: Options | undefined, +): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; +/** + * @template {IncomingMessage} [RequestInternal=IncomingMessage] + * @template {ServerResponse} [ResponseInternal=ServerResponse] + * @param {Compiler | MultiCompiler} compiler compiler + * @param {Options=} options options + * @returns {API} webpack dev middleware + */ +declare function plugin< + RequestInternal extends IncomingMessage = import("http").IncomingMessage, + ResponseInternal extends ServerResponse = ServerResponse, +>( + compiler: Compiler | MultiCompiler, + options?: Options | undefined, +): API; type Schema = import("schema-utils/declarations/validate").Schema; type Compiler = import("webpack").Compiler; type MultiCompiler = import("webpack").MultiCompiler; From 4a799c08baa8bbfcdf0422c6c502e451e7238ddf Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Fri, 6 Mar 2026 19:43:15 +0000 Subject: [PATCH 02/15] feat: enhance type definitions to include isPlugin property and add logging tests --- package-lock.json | 34 ++++++-- test/logging.test.js | 198 ++++++++++++++++++++++++++++++++++++++++++- types/index.d.ts | 19 ++--- 3 files changed, 231 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5bf7684b0..50b923406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -132,6 +132,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2247,6 +2248,7 @@ "integrity": "sha512-lf6d+BdMkJIFCxx2FpajLpqVGGyaGUNFU6jhEM6QUPeGuoA5et2kJXrL0NSY2uWLOVyYYc/FPjzlbe8trA9tBQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -2328,7 +2330,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.19.tgz", "integrity": "sha512-VYHtPnZt/Zd/ATbW3rtexWpBnHUohUrQOHff/2JBhsVgxOrksAxJnLAO43Q1ayLJBJUUwNVo+RU0sx0aaysZfg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.2", @@ -2468,14 +2471,16 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.14.tgz", "integrity": "sha512-2bf7n+kS92g+cMKV0wr9o/Oq9n8JzU7CcrB96gIh2GHgnF+0xDOqO2W/1KeFAqOfqosoOVE48t+4dnEMkkoJ2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -2673,7 +2678,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -5570,6 +5576,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5702,6 +5709,7 @@ "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5795,6 +5803,7 @@ "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", @@ -5834,6 +5843,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -6536,6 +6546,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6578,6 +6589,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7261,6 +7273,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -10432,6 +10445,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10521,6 +10535,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -11994,6 +12009,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13424,6 +13440,7 @@ "integrity": "sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -14445,6 +14462,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -18735,6 +18753,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -21149,6 +21168,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -21328,7 +21348,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsscmp": { "version": "1.0.6", @@ -21482,6 +21503,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21851,6 +21873,7 @@ "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -22418,6 +22441,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/test/logging.test.js b/test/logging.test.js index aa9109818..448941084 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -1,10 +1,15 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; - import { stripVTControlCharacters } from "node:util"; + +import Hapi from "@hapi/hapi"; import execa from "execa"; +import middleware from "../src"; +import webpackConfig from "./fixtures/webpack.config"; +import getCompiler from "./helpers/getCompiler"; + function extractErrorEntry(string) { const matches = string.match(/error:\s\D[^:||\n||\r]+/gim); @@ -1474,3 +1479,194 @@ describe("logging", () => { }); }); }); + +async function waitUntilCompiled(instance) { + await new Promise((resolve, reject) => { + const startedAt = Date.now(); + const timeout = 10000; + const interval = setInterval(() => { + if (instance.context.state) { + clearInterval(interval); + resolve(); + } else if (Date.now() - startedAt > timeout) { + clearInterval(interval); + reject(new Error("Compilation did not finish in time")); + } + }, 20); + }); +} + +async function closeInstance(instance) { + if (!instance.context.watching.closed) { + await new Promise((resolve, reject) => { + instance.close((error) => { + if (error) { + reject(error); + return; + } + + resolve(); + }); + }); + } +} + +describe("logging without plugin APIs", () => { + /** @type {import("webpack").Compiler} */ + let compiler; + /** @type {{ log: jest.Mock }} */ + let fakeLogger; + /** @type {jest.SpyInstance} */ + let getInfrastructureLoggerSpy; + /** @type {jest.SpyInstance} */ + let consoleLogSpy; + + beforeEach(() => { + fakeLogger = { + log: jest.fn(), + }; + + compiler = getCompiler(webpackConfig); + getInfrastructureLoggerSpy = jest + .spyOn(compiler, "getInfrastructureLogger") + .mockReturnValue(fakeLogger); + consoleLogSpy = jest.spyOn(globalThis.console, "log").mockImplementation(); + }); + + afterEach(() => { + getInfrastructureLoggerSpy.mockRestore(); + consoleLogSpy.mockRestore(); + }); + + it("should use console.log in middleware()", async () => { + const instance = middleware(compiler); + + await waitUntilCompiled(instance); + + expect(consoleLogSpy).toHaveBeenCalled(); + expect(fakeLogger.log).toHaveBeenCalled(); + + await closeInstance(instance); + }); + + it("should use console.log in koaWrapper()", async () => { + const wrapper = middleware.koaWrapper(compiler); + + await waitUntilCompiled(wrapper.devMiddleware); + + expect(consoleLogSpy).toHaveBeenCalled(); + expect(fakeLogger.log).toHaveBeenCalled(); + + await closeInstance(wrapper.devMiddleware); + }); + + it("should use console.log in honoWrapper()", async () => { + const wrapper = middleware.honoWrapper(compiler); + + await waitUntilCompiled(wrapper.devMiddleware); + + expect(consoleLogSpy).toHaveBeenCalled(); + expect(fakeLogger.log).toHaveBeenCalled(); + + await closeInstance(wrapper.devMiddleware); + }); + + it("should use console.log in hapiWrapper()", async () => { + const server = Hapi.server(); + + await server.register({ + plugin: middleware.hapiWrapper(), + options: { + compiler, + }, + }); + + await waitUntilCompiled(server.webpackDevMiddleware); + + expect(consoleLogSpy).toHaveBeenCalled(); + expect(fakeLogger.log).toHaveBeenCalled(); + + await closeInstance(server.webpackDevMiddleware); + await server.stop(); + }); +}); + +describe("logging with plugin APIs", () => { + /** @type {import("webpack").Compiler} */ + let compiler; + /** @type {import("webpack").Logger} */ + let fakeLogger; + /** @type {jest.SpyInstance} */ + let getInfrastructureLoggerSpy; + /** @type {jest.SpyInstance} */ + let consoleLogSpy; + + beforeEach(() => { + fakeLogger = { + log: jest.fn(), + }; + + compiler = getCompiler(webpackConfig); + getInfrastructureLoggerSpy = jest + .spyOn(compiler, "getInfrastructureLogger") + .mockReturnValue(fakeLogger); + consoleLogSpy = jest.spyOn(globalThis.console, "log").mockImplementation(); + }); + + afterEach(() => { + getInfrastructureLoggerSpy.mockRestore(); + consoleLogSpy.mockRestore(); + }); + + it("should use infrastructure logger in plugin()", async () => { + const instance = middleware.plugin(compiler); + + await waitUntilCompiled(instance); + + expect(fakeLogger.log).toHaveBeenCalled(); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + await closeInstance(instance); + }); + + it("should use infrastructure logger in koaPluginWrapper()", async () => { + const wrapper = middleware.koaPluginWrapper(compiler); + + await waitUntilCompiled(wrapper.devMiddleware); + + expect(fakeLogger.log).toHaveBeenCalled(); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + await closeInstance(wrapper.devMiddleware); + }); + + it("should use infrastructure logger in honoPluginWrapper()", async () => { + const wrapper = middleware.honoPluginWrapper(compiler); + + await waitUntilCompiled(wrapper.devMiddleware); + + expect(fakeLogger.log).toHaveBeenCalled(); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + await closeInstance(wrapper.devMiddleware); + }); + + it("should use infrastructure logger in hapiPluginWrapper()", async () => { + const server = Hapi.server(); + + await server.register({ + plugin: middleware.hapiPluginWrapper(), + options: { + compiler, + }, + }); + + await waitUntilCompiled(server.webpackDevMiddleware); + + expect(fakeLogger.log).toHaveBeenCalled(); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + await closeInstance(server.webpackDevMiddleware); + await server.stop(); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index d5cb9e54c..213523f46 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -63,6 +63,7 @@ export = wdm; * @property {Watching | MultiWatching | undefined} watching watching * @property {Logger} logger logger * @property {OutputFileSystem} outputFileSystem output file system + * @property {boolean} isPlugin whether wdm is used as webpack plugin */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] @@ -212,20 +213,6 @@ declare namespace wdm { HapiOptions, }; } -/** - * @template S - * @template O - * @typedef {object} HapiPluginBase - * @property {(server: S, options: O) => void | Promise} register register - */ -/** - * @template S - * @template O - * @typedef {HapiPluginBase & { pkg: { name: string }, multiple: boolean }} HapiPlugin - */ -/** - * @typedef {Options & { compiler: Compiler | MultiCompiler }} HapiOptions - */ /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal @@ -403,6 +390,10 @@ type Context< * output file system */ outputFileSystem: OutputFileSystem; + /** + * whether wdm is used as webpack plugin + */ + isPlugin: boolean; }; type FilledContext< RequestInternal extends IncomingMessage = import("http").IncomingMessage, From f149b0a69a626e14eb5c5ce4a695d82be40f258b Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Fri, 6 Mar 2026 20:06:39 +0000 Subject: [PATCH 03/15] docs: add plugin mode support and wrappers for Koa, Hapi, and Hono integrations --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 46a54691c..6deac40ae 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,37 @@ instance.waitUntilValid(() => { }); ``` +### `plugin(compiler, options)` + +Creates middleware instance in plugin mode. + +In plugin mode, stats output is written through webpack infrastructure logger +(`compiler.getInfrastructureLogger`) instead of `console.log`. + +```js +const webpack = require("webpack"); +const middleware = require("webpack-dev-middleware"); + +const compiler = webpack({ + /* Webpack configuration */ +}); + +const instance = middleware.plugin(compiler, { + /* webpack-dev-middleware options */ +}); +``` + +### Plugin wrappers + +The following wrappers enable plugin mode for framework integrations: + +- `middleware.koaPluginWrapper(compiler, options)` +- `middleware.hapiPluginWrapper()` +- `middleware.honoPluginWrapper(compiler, options)` + +They are equivalent to `koaWrapper`/`hapiWrapper`/`honoWrapper`, but use plugin +mode logging behavior. + ### `forwardError` Type: `boolean` @@ -721,11 +752,17 @@ const devMiddlewareOptions = { }; const app = new Koa(); -app.use(middleware.koaWrapper(compiler, devMiddlewareOptions)); +app.use(middleware.koaPluginWrapper(compiler, devMiddlewareOptions)); app.listen(3000); ``` +Non-plugin variant: + +```js +app.use(middleware.koaWrapper(compiler, devMiddlewareOptions)); +``` + ### Hapi ```js @@ -740,7 +777,7 @@ const devMiddlewareOptions = {}; const server = Hapi.server({ port: 3000, host: "localhost" }); await server.register({ - plugin: devMiddleware.hapiPlugin(), + plugin: devMiddleware.hapiPluginWrapper(), options: { // The `compiler` option is required compiler, @@ -758,6 +795,19 @@ process.on("unhandledRejection", (err) => { }); ``` +Non-plugin variant: + +```js +await server.register({ + plugin: devMiddleware.hapiWrapper(), + options: { + // The `compiler` option is required + compiler, + ...devMiddlewareOptions, + }, +}); +``` + ### Fastify Fastify interop will require the use of `fastify-express` instead of `middie` for providing middleware support. As the authors of `fastify-express` recommend, this should only be used as a stopgap while full Fastify support is worked on. @@ -794,11 +844,17 @@ const devMiddlewareOptions = { const app = new Hono(); -app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions)); +app.use(devMiddleware.honoPluginWrapper(compiler, devMiddlewareOptions)); serve(app); ``` +Non-plugin variant: + +```js +app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions)); +``` + ## Contributing Please take a moment to read our contributing guidelines if you haven't yet done so. From 16af669ff5d6017e0622f9741d02a87188cd670f Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 10 Mar 2026 20:44:39 +0300 Subject: [PATCH 04/15] refactor: avoid printing stats at all for the plugin usage --- src/utils/setupHooks.js | 173 ++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 87 deletions(-) diff --git a/src/utils/setupHooks.js b/src/utils/setupHooks.js index abd88205c..c1be8c147 100644 --- a/src/utils/setupHooks.js +++ b/src/utils/setupHooks.js @@ -54,106 +54,105 @@ function setupHooks(context) { // We are now on valid state context.state = true; - context.stats = stats; - // Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling - process.nextTick(() => { - const { compiler, logger, options, state, callbacks } = context; - - // Check if still in valid state - if (!state) { - return; - } - - logger.log("Compilation finished"); - - const isMultiCompilerMode = Boolean( - /** @type {MultiCompiler} */ - (compiler).compilers, - ); - - /** - * @type {StatsOptions | MultiStatsOptions | undefined} - */ - let statsOptions; - - if (typeof options.stats !== "undefined") { - statsOptions = isMultiCompilerMode - ? { - children: - /** @type {MultiCompiler} */ - (compiler).compilers.map(() => options.stats), - } - : options.stats; - } else { - statsOptions = isMultiCompilerMode - ? { - children: - /** @type {MultiCompiler} */ - (compiler).compilers.map((child) => child.options.stats), - } - : /** @type {Compiler} */ (compiler).options.stats; - } - - if (isMultiCompilerMode) { - /** @type {MultiStatsOptions} */ - (statsOptions).children = - /** @type {MultiStatsOptions} */ - (statsOptions).children.map( - /** - * @param {StatsOptions} childStatsOptions child stats options - * @returns {StatsObjectOptions} object child stats options - */ - (childStatsOptions) => { - childStatsOptions = normalizeStatsOptions(childStatsOptions); - - if (typeof childStatsOptions.colors === "undefined") { - const [firstCompiler] = - /** @type {MultiCompiler} */ - (compiler).compilers; + // For plugin support we should print nothing, because webpack/webpack-cli/webpack-dev-server will print them on using `stats.toString()` + if (!context.isPlugin) { + // Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling + process.nextTick(() => { + const { compiler, logger, options, state, callbacks } = context; - childStatsOptions.colors = - firstCompiler.webpack.cli.isColorSupported(); - } + // Check if still in valid state + if (!state) { + return; + } - return childStatsOptions; - }, - ); - } else { - statsOptions = normalizeStatsOptions( - /** @type {StatsOptions} */ (statsOptions), + logger.log("Compilation finished"); + + const isMultiCompilerMode = Boolean( + /** @type {MultiCompiler} */ + (compiler).compilers, ); - if (typeof statsOptions.colors === "undefined") { - const { compiler } = /** @type {{ compiler: Compiler }} */ (context); - statsOptions.colors = compiler.webpack.cli.isColorSupported(); + /** + * @type {StatsOptions | MultiStatsOptions | undefined} + */ + let statsOptions; + + if (typeof options.stats !== "undefined") { + statsOptions = isMultiCompilerMode + ? { + children: + /** @type {MultiCompiler} */ + (compiler).compilers.map(() => options.stats), + } + : options.stats; + } else { + statsOptions = isMultiCompilerMode + ? { + children: + /** @type {MultiCompiler} */ + (compiler).compilers.map((child) => child.options.stats), + } + : /** @type {Compiler} */ (compiler).options.stats; } - } - - const printedStats = stats.toString( - /** @type {StatsObjectOptions} */ - (statsOptions), - ); - - // Avoid extra empty line when `stats: 'none'` - if (printedStats) { - if (context.isPlugin) { - // Use logger when used as webpack plugin - logger.log(printedStats); + + if (isMultiCompilerMode) { + /** @type {MultiStatsOptions} */ + (statsOptions).children = + /** @type {MultiStatsOptions} */ + (statsOptions).children.map( + /** + * @param {StatsOptions} childStatsOptions child stats options + * @returns {StatsObjectOptions} object child stats options + */ + (childStatsOptions) => { + childStatsOptions = normalizeStatsOptions(childStatsOptions); + + if (typeof childStatsOptions.colors === "undefined") { + const [firstCompiler] = + /** @type {MultiCompiler} */ + (compiler).compilers; + + childStatsOptions.colors = + firstCompiler.webpack.cli.isColorSupported(); + } + + return childStatsOptions; + }, + ); } else { + statsOptions = normalizeStatsOptions( + /** @type {StatsOptions} */ (statsOptions), + ); + + if (typeof statsOptions.colors === "undefined") { + const { compiler } = /** @type {{ compiler: Compiler }} */ ( + context + ); + statsOptions.colors = compiler.webpack.cli.isColorSupported(); + } + } + + const printedStats = stats.toString( + /** @type {StatsObjectOptions} */ + (statsOptions), + ); + + // Avoid extra empty line when `stats: 'none'` + if (printedStats) { // eslint-disable-next-line no-console console.log(printedStats); } - } - context.callbacks = []; + context.callbacks = []; - // Execute callback that are delayed - for (const callback of callbacks) { - callback(stats); - } - }); + // Execute callback that are delayed + for (const callback of callbacks) { + callback(stats); + } + }); + } } // eslint-disable-next-line prefer-destructuring From a2524460f7b3602f6e53b64b075fb8ce74535aa0 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 10 Mar 2026 20:48:26 +0300 Subject: [PATCH 05/15] fix: logic --- src/utils/setupHooks.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/utils/setupHooks.js b/src/utils/setupHooks.js index c1be8c147..d51c8bf8f 100644 --- a/src/utils/setupHooks.js +++ b/src/utils/setupHooks.js @@ -56,17 +56,17 @@ function setupHooks(context) { context.state = true; context.stats = stats; - // For plugin support we should print nothing, because webpack/webpack-cli/webpack-dev-server will print them on using `stats.toString()` - if (!context.isPlugin) { - // Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling - process.nextTick(() => { - const { compiler, logger, options, state, callbacks } = context; - - // Check if still in valid state - if (!state) { - return; - } + // Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling + process.nextTick(() => { + const { compiler, logger, options, state, callbacks } = context; + + // Check if still in valid state + if (!state) { + return; + } + // For plugin support we should print nothing, because webpack/webpack-cli/webpack-dev-server will print them on using `stats.toString()` + if (!context.isPlugin) { logger.log("Compilation finished"); const isMultiCompilerMode = Boolean( @@ -144,15 +144,15 @@ function setupHooks(context) { // eslint-disable-next-line no-console console.log(printedStats); } + } - context.callbacks = []; + context.callbacks = []; - // Execute callback that are delayed - for (const callback of callbacks) { - callback(stats); - } - }); - } + // Execute callback that are delayed + for (const callback of callbacks) { + callback(stats); + } + }); } // eslint-disable-next-line prefer-destructuring From 94f842292f7a4e4cf50044764cd2ae747698261a Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 10 Mar 2026 21:05:18 +0300 Subject: [PATCH 06/15] refactor: fix logic --- README.md | 66 +++++++++++++++++++++++---------------------- src/index.js | 75 ++++++++++++++++++++++++---------------------------- 2 files changed, 70 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 6deac40ae..46b8a83b9 100644 --- a/README.md +++ b/README.md @@ -481,7 +481,7 @@ instance.waitUntilValid(() => { Creates middleware instance in plugin mode. -In plugin mode, stats output is written through webpack infrastructure logger +In plugin mode, stats output is written through custom code (i.e. in callback for `watch` or where you are calling `stats.toString(options)`) (`compiler.getInfrastructureLogger`) instead of `console.log`. ```js @@ -489,11 +489,25 @@ const webpack = require("webpack"); const middleware = require("webpack-dev-middleware"); const compiler = webpack({ + plugins: [ + { + apply(compiler) { + const devMiddleware = middleware.plugin(compiler, { + /* webpack-dev-middleware options */ + }); + }, + }, + ], /* Webpack configuration */ }); -const instance = middleware.plugin(compiler, { - /* webpack-dev-middleware options */ +compiler.watch((err, stats) => { + if (err) { + console.error(err); + return; + } + + console.log(stats.toString()); }); ``` @@ -752,17 +766,13 @@ const devMiddlewareOptions = { }; const app = new Koa(); -app.use(middleware.koaPluginWrapper(compiler, devMiddlewareOptions)); +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.koaPluginWrapper(compiler, devMiddlewareOptions)); app.listen(3000); ``` -Non-plugin variant: - -```js -app.use(middleware.koaWrapper(compiler, devMiddlewareOptions)); -``` - ### Hapi ```js @@ -777,7 +787,7 @@ const devMiddlewareOptions = {}; const server = Hapi.server({ port: 3000, host: "localhost" }); await server.register({ - plugin: devMiddleware.hapiPluginWrapper(), + plugin: devMiddleware.hapiWrapper(), options: { // The `compiler` option is required compiler, @@ -785,6 +795,16 @@ await server.register({ }, }); +// 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.hapiPluginWrapper(), +// options: { +// // The `compiler` option is required +// compiler, +// ...devMiddlewareOptions, +// }, +// }); + await server.start(); console.log("Server running on %s", server.info.uri); @@ -795,19 +815,6 @@ process.on("unhandledRejection", (err) => { }); ``` -Non-plugin variant: - -```js -await server.register({ - plugin: devMiddleware.hapiWrapper(), - options: { - // The `compiler` option is required - compiler, - ...devMiddlewareOptions, - }, -}); -``` - ### Fastify Fastify interop will require the use of `fastify-express` instead of `middie` for providing middleware support. As the authors of `fastify-express` recommend, this should only be used as a stopgap while full Fastify support is worked on. @@ -844,15 +851,12 @@ const devMiddlewareOptions = { const app = new Hono(); -app.use(devMiddleware.honoPluginWrapper(compiler, devMiddlewareOptions)); - -serve(app); -``` +app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions)); -Non-plugin variant: +// Alternative usage (when you want to use as a plugin, i.e. all stats will be printed by other code): +// const honoDevMiddleware = devMiddleware.honoPluginWrapper(compiler, devMiddlewareOptions) -```js -app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions)); +serve(app); ``` ## Contributing diff --git a/src/index.js b/src/index.js index 19b30a63b..6651d83dc 100644 --- a/src/index.js +++ b/src/index.js @@ -196,9 +196,10 @@ const noop = () => {}; * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options + * @param {boolean} isPlugin true when will use as a plugin, otherwise false * @returns {API} webpack dev middleware */ -function wdm(compiler, options = {}) { +function wdm(compiler, options = {}, isPlugin = false) { validate(/** @type {Schema} */ (schema), options, { name: "Dev Middleware", baseDataPath: "options", @@ -220,7 +221,6 @@ function wdm(compiler, options = {}) { */ const context = { state: false, - stats: undefined, callbacks: [], options, @@ -238,36 +238,38 @@ function wdm(compiler, options = {}) { setupOutputFileSystem(context); // Start watching - if (/** @type {Compiler} */ (context.compiler).watching) { - context.watching = /** @type {Compiler} */ (context.compiler).watching; - } else { - /** - * @param {Error | null | undefined} error error - */ - const errorHandler = (error) => { - if (error) { - // TODO: improve that in future - // For example - `writeToDisk` can throw an error and right now it is ends watching. - // We can improve that and keep watching active, but it is require API on webpack side. - // Let's implement that in webpack@5 because it is rare case. - context.logger.error(error); - } - }; + if (!isPlugin) { + if (/** @type {Compiler} */ (context.compiler).watching) { + context.watching = /** @type {Compiler} */ (context.compiler).watching; + } else { + /** + * @param {Error | null | undefined} error error + */ + const errorHandler = (error) => { + if (error) { + // TODO: improve that in future + // For example - `writeToDisk` can throw an error and right now it is ends watching. + // We can improve that and keep watching active, but it is require API on webpack side. + // Let's implement that in webpack@5 because it is rare case. + context.logger.error(error); + } + }; - if ( - Array.isArray(/** @type {MultiCompiler} */ (context.compiler).compilers) - ) { - const compilers = /** @type {MultiCompiler} */ (context.compiler); - const watchOptions = compilers.compilers.map( - (childCompiler) => childCompiler.options.watchOptions || {}, - ); + if ( + Array.isArray(/** @type {MultiCompiler} */ (context.compiler).compilers) + ) { + const compilers = /** @type {MultiCompiler} */ (context.compiler); + const watchOptions = compilers.compilers.map( + (childCompiler) => childCompiler.options.watchOptions || {}, + ); - context.watching = compiler.watch(watchOptions, errorHandler); - } else { - const oneCompiler = /** @type {Compiler} */ (context.compiler); - const watchOptions = oneCompiler.options.watchOptions || {}; + context.watching = compiler.watch(watchOptions, errorHandler); + } else { + const oneCompiler = /** @type {Compiler} */ (context.compiler); + const watchOptions = oneCompiler.options.watchOptions || {}; - context.watching = compiler.watch(watchOptions, errorHandler); + context.watching = compiler.watch(watchOptions, errorHandler); + } } } @@ -338,7 +340,7 @@ function createHapiWrapper(usePlugin = false) { throw new Error("The compiler options is required."); } - const devMiddleware = wdm(compiler, rest); + const devMiddleware = wdm(compiler, rest, usePlugin); if (usePlugin) { // Use logger when used as webpack plugin @@ -425,7 +427,7 @@ wdm.hapiPluginWrapper = hapiPluginWrapper; * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper */ function createKoaWrapper(compiler, options, usePlugin = false) { - const devMiddleware = wdm(compiler, options); + const devMiddleware = wdm(compiler, options, usePlugin); if (usePlugin) { devMiddleware.context.isPlugin = true; @@ -574,11 +576,7 @@ wdm.koaPluginWrapper = koaPluginWrapper; * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono wrapper */ function createHonoWrapper(compiler, options, usePlugin = false) { - const devMiddleware = wdm(compiler, options); - - if (usePlugin) { - devMiddleware.context.isPlugin = true; - } + const devMiddleware = wdm(compiler, options, usePlugin); /** * @param {{ env: EXPECTED_ANY, body: EXPECTED_ANY, json: EXPECTED_ANY, status: EXPECTED_ANY, set: EXPECTED_ANY, req: RequestInternal & import("./utils/compatibleAPI").ExpectedIncomingMessage & { header: (name: string) => string }, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: EXPECTED_ANY, status: EXPECTED_ANY } }} context context @@ -778,10 +776,7 @@ wdm.honoPluginWrapper = honoPluginWrapper; * @returns {API} webpack dev middleware */ function plugin(compiler, options = {}) { - const instance = wdm(compiler, options); - // Mark that wdm is used as webpack plugin (to use logger instead of console.log) - instance.context.isPlugin = true; - return instance; + return wdm(compiler, options, true); } wdm.plugin = plugin; From 937c1be27583183ceb9087e701f9722ed12479db Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Tue, 10 Mar 2026 21:36:52 +0300 Subject: [PATCH 07/15] test: refactor --- test/logging.test.js | 196 ------------------------------------------- 1 file changed, 196 deletions(-) diff --git a/test/logging.test.js b/test/logging.test.js index 448941084..c80fb32db 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -3,13 +3,8 @@ import os from "node:os"; import path from "node:path"; import { stripVTControlCharacters } from "node:util"; -import Hapi from "@hapi/hapi"; import execa from "execa"; -import middleware from "../src"; -import webpackConfig from "./fixtures/webpack.config"; -import getCompiler from "./helpers/getCompiler"; - function extractErrorEntry(string) { const matches = string.match(/error:\s\D[^:||\n||\r]+/gim); @@ -1479,194 +1474,3 @@ describe("logging", () => { }); }); }); - -async function waitUntilCompiled(instance) { - await new Promise((resolve, reject) => { - const startedAt = Date.now(); - const timeout = 10000; - const interval = setInterval(() => { - if (instance.context.state) { - clearInterval(interval); - resolve(); - } else if (Date.now() - startedAt > timeout) { - clearInterval(interval); - reject(new Error("Compilation did not finish in time")); - } - }, 20); - }); -} - -async function closeInstance(instance) { - if (!instance.context.watching.closed) { - await new Promise((resolve, reject) => { - instance.close((error) => { - if (error) { - reject(error); - return; - } - - resolve(); - }); - }); - } -} - -describe("logging without plugin APIs", () => { - /** @type {import("webpack").Compiler} */ - let compiler; - /** @type {{ log: jest.Mock }} */ - let fakeLogger; - /** @type {jest.SpyInstance} */ - let getInfrastructureLoggerSpy; - /** @type {jest.SpyInstance} */ - let consoleLogSpy; - - beforeEach(() => { - fakeLogger = { - log: jest.fn(), - }; - - compiler = getCompiler(webpackConfig); - getInfrastructureLoggerSpy = jest - .spyOn(compiler, "getInfrastructureLogger") - .mockReturnValue(fakeLogger); - consoleLogSpy = jest.spyOn(globalThis.console, "log").mockImplementation(); - }); - - afterEach(() => { - getInfrastructureLoggerSpy.mockRestore(); - consoleLogSpy.mockRestore(); - }); - - it("should use console.log in middleware()", async () => { - const instance = middleware(compiler); - - await waitUntilCompiled(instance); - - expect(consoleLogSpy).toHaveBeenCalled(); - expect(fakeLogger.log).toHaveBeenCalled(); - - await closeInstance(instance); - }); - - it("should use console.log in koaWrapper()", async () => { - const wrapper = middleware.koaWrapper(compiler); - - await waitUntilCompiled(wrapper.devMiddleware); - - expect(consoleLogSpy).toHaveBeenCalled(); - expect(fakeLogger.log).toHaveBeenCalled(); - - await closeInstance(wrapper.devMiddleware); - }); - - it("should use console.log in honoWrapper()", async () => { - const wrapper = middleware.honoWrapper(compiler); - - await waitUntilCompiled(wrapper.devMiddleware); - - expect(consoleLogSpy).toHaveBeenCalled(); - expect(fakeLogger.log).toHaveBeenCalled(); - - await closeInstance(wrapper.devMiddleware); - }); - - it("should use console.log in hapiWrapper()", async () => { - const server = Hapi.server(); - - await server.register({ - plugin: middleware.hapiWrapper(), - options: { - compiler, - }, - }); - - await waitUntilCompiled(server.webpackDevMiddleware); - - expect(consoleLogSpy).toHaveBeenCalled(); - expect(fakeLogger.log).toHaveBeenCalled(); - - await closeInstance(server.webpackDevMiddleware); - await server.stop(); - }); -}); - -describe("logging with plugin APIs", () => { - /** @type {import("webpack").Compiler} */ - let compiler; - /** @type {import("webpack").Logger} */ - let fakeLogger; - /** @type {jest.SpyInstance} */ - let getInfrastructureLoggerSpy; - /** @type {jest.SpyInstance} */ - let consoleLogSpy; - - beforeEach(() => { - fakeLogger = { - log: jest.fn(), - }; - - compiler = getCompiler(webpackConfig); - getInfrastructureLoggerSpy = jest - .spyOn(compiler, "getInfrastructureLogger") - .mockReturnValue(fakeLogger); - consoleLogSpy = jest.spyOn(globalThis.console, "log").mockImplementation(); - }); - - afterEach(() => { - getInfrastructureLoggerSpy.mockRestore(); - consoleLogSpy.mockRestore(); - }); - - it("should use infrastructure logger in plugin()", async () => { - const instance = middleware.plugin(compiler); - - await waitUntilCompiled(instance); - - expect(fakeLogger.log).toHaveBeenCalled(); - expect(consoleLogSpy).not.toHaveBeenCalled(); - - await closeInstance(instance); - }); - - it("should use infrastructure logger in koaPluginWrapper()", async () => { - const wrapper = middleware.koaPluginWrapper(compiler); - - await waitUntilCompiled(wrapper.devMiddleware); - - expect(fakeLogger.log).toHaveBeenCalled(); - expect(consoleLogSpy).not.toHaveBeenCalled(); - - await closeInstance(wrapper.devMiddleware); - }); - - it("should use infrastructure logger in honoPluginWrapper()", async () => { - const wrapper = middleware.honoPluginWrapper(compiler); - - await waitUntilCompiled(wrapper.devMiddleware); - - expect(fakeLogger.log).toHaveBeenCalled(); - expect(consoleLogSpy).not.toHaveBeenCalled(); - - await closeInstance(wrapper.devMiddleware); - }); - - it("should use infrastructure logger in hapiPluginWrapper()", async () => { - const server = Hapi.server(); - - await server.register({ - plugin: middleware.hapiPluginWrapper(), - options: { - compiler, - }, - }); - - await waitUntilCompiled(server.webpackDevMiddleware); - - expect(fakeLogger.log).toHaveBeenCalled(); - expect(consoleLogSpy).not.toHaveBeenCalled(); - - await closeInstance(server.webpackDevMiddleware); - await server.stop(); - }); -}); From 9e1cc762d3b75176e3445946fd11c3b122e7a76b Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 00:18:41 +0300 Subject: [PATCH 08/15] test: added --- .../logging.test.js.snap.webpack5 | 681 ++++++++++++++++-- test/helpers/runner.js | 152 ++-- test/logging.test.js | 15 +- 3 files changed, 729 insertions(+), 119 deletions(-) diff --git a/test/__snapshots__/logging.test.js.snap.webpack5 b/test/__snapshots__/logging.test.js.snap.webpack5 index e5a367df7..b931e29dc 100644 --- a/test/__snapshots__/logging.test.js.snap.webpack5 +++ b/test/__snapshots__/logging.test.js.snap.webpack5 @@ -1,18 +1,18 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`logging should logging an error in "watch" method: stderr 1`] = `"Error: Watch error"`; +exports[`logging plugin should logging an error in "watch" method: stderr 1`] = `"Error: Watch error"`; -exports[`logging should logging an warning: stderr 1`] = `""`; +exports[`logging plugin should logging an warning: stderr 1`] = `""`; -exports[`logging should logging an warning: stdout 1`] = ` +exports[`logging plugin should logging an warning: stdout 1`] = ` "WARNING in Warning webpack compiled with 1 warning" `; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #2: stderr 1`] = `""`; +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #2: stderr 1`] = `""`; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #2: stdout 1`] = ` +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #2: stdout 1`] = ` "broken: asset bundle.js x KiB [emitted] (name: main) ./broken.js x bytes [built] [code generated] [1 error] @@ -45,9 +45,9 @@ cacheable modules x bytes success (webpack x.x.x) compiled successfully in x ms" `; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #3: stderr 1`] = `""`; +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #3: stderr 1`] = `""`; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #3: stdout 1`] = ` +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #3: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) ./broken.js x bytes [built] [code generated] [1 error] @@ -77,9 +77,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #4: stderr 1`] = `""`; +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #4: stderr 1`] = `""`; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #4: stdout 1`] = ` +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #4: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) ./broken.js x bytes [built] [code generated] [1 error] @@ -103,9 +103,9 @@ asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" `; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #5: stderr 1`] = `""`; +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #5: stderr 1`] = `""`; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration #5: stdout 1`] = ` +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration #5: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) ./bar.js x bytes [built] [code generated] webpack x.x.x compiled successfully in x ms @@ -121,9 +121,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration: stderr 1`] = `""`; +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration: stderr 1`] = `""`; -exports[`logging should logging in multi-compiler and respect the "stats" option from configuration: stdout 1`] = ` +exports[`logging plugin should logging in multi-compiler and respect the "stats" option from configuration: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) ./broken.js x bytes [built] [code generated] [1 error] @@ -153,9 +153,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build and respect colors #2: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect colors #2: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect colors #2: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect colors #2: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -167,9 +167,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build and respect colors: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect colors: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect colors: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -181,9 +181,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build and respect the "NO_COLOR" env: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "NO_COLOR" env: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "NO_COLOR" env: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect the "NO_COLOR" env: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -195,33 +195,33 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with custom object value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with custom object value: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with custom object value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with custom object value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" `; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "false" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "false" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "false" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "false" value: stdout 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "minimal" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "minimal" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "minimal" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "minimal" value: stdout 1`] = ` "x assets x modules webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "none" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "none" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "none" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "none" value: stdout 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "true" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "true" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "true" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "true" value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -233,9 +233,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "verbose" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "verbose" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build and respect the "stats" option from configuration with the "verbose" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build and respect the "stats" option from configuration with the "verbose" value: stdout 1`] = ` "PublicPath: auto asset bundle.js x KiB {main} [emitted] (name: main) asset svg.svg x KiB ({main}) [emitted] [from: svg.svg] (auxiliary name: main) @@ -276,9 +276,9 @@ cjs require ./svg.svg [./foo.js] 3:0-20 LOG from xxx" `; -exports[`logging should logging on successfully build in multi-compiler mode: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build in multi-compiler mode: stderr 1`] = `""`; -exports[`logging should logging on successfully build in multi-compiler mode: stdout 1`] = ` +exports[`logging plugin should logging on successfully build in multi-compiler mode: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -294,33 +294,33 @@ asset bundle.js x KiB [emitted] (name: main) webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" `; -exports[`logging should logging on successfully build using the "stats" option for middleware with object value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with object value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" `; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "none" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "none" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "none" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "none" value: stdout 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "normal" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "normal" value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -332,9 +332,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "true" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "true" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "true" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "true" value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -346,9 +346,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "verbose" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "verbose" value: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the "verbose" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "verbose" value: stdout 1`] = ` "PublicPath: auto asset bundle.js x KiB {main} [emitted] (name: main) asset svg.svg x KiB ({main}) [emitted] [from: svg.svg] (auxiliary name: main) @@ -389,17 +389,17 @@ cjs require ./svg.svg [./foo.js] 3:0-20 LOG from xxx" `; -exports[`logging should logging on successfully build using the "stats" option for middleware with the object value and colors: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the object value and colors: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with the object value and colors: stdout 1`] = ` +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the object value and colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" `; -exports[`logging should logging on successfully build when the 'stats' doesn't exist: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build when the 'stats' doesn't exist: stderr 1`] = `""`; -exports[`logging should logging on successfully build when the 'stats' doesn't exist: stdout 1`] = ` +exports[`logging plugin should logging on successfully build when the 'stats' doesn't exist: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -411,9 +411,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully build: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully build: stderr 1`] = `""`; -exports[`logging should logging on successfully build: stdout 1`] = ` +exports[`logging plugin should logging on successfully build: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -425,9 +425,9 @@ cacheable modules x bytes webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors: stderr 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors: stdout 1`] = ` +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -435,9 +435,9 @@ asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) asset bundle.js x KiB [emitted] (name: main)" `; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -445,13 +445,13 @@ asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) asset bundle.js x KiB [emitted] (name: main)" `; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -467,9 +467,9 @@ asset bundle.js x KiB [emitted] (name: main) webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value: stderr 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value: stdout 1`] = ` +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -485,9 +485,9 @@ asset bundle.js x KiB [emitted] (name: main) webpack x.x.x compiled successfully in x ms" `; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the object value: stderr 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the object value: stderr 1`] = `""`; -exports[`logging should logging on successfully multi-compiler build using the "stats" option for middleware with the object value: stdout 1`] = ` +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the object value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) @@ -495,9 +495,9 @@ asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) asset bundle.js x KiB [emitted] (name: main)" `; -exports[`logging should logging on unsuccessful build in multi-compiler: stderr 1`] = `""`; +exports[`logging plugin should logging on unsuccessful build in multi-compiler: stderr 1`] = `""`; -exports[`logging should logging on unsuccessful build in multi-compiler: stdout 1`] = ` +exports[`logging plugin should logging on unsuccessful build in multi-compiler: stdout 1`] = ` "ERROR in ./broken.js 1:3 Module parse failed: Unexpected token (1:3) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders @@ -515,9 +515,9 @@ You may need an appropriate loader to handle this file type, currently no loader webpack compiled with 1 error" `; -exports[`logging should logging on unsuccessful build: stderr 1`] = `""`; +exports[`logging plugin should logging on unsuccessful build: stderr 1`] = `""`; -exports[`logging should logging on unsuccessful build: stdout 1`] = ` +exports[`logging plugin should logging on unsuccessful build: stdout 1`] = ` "ERROR in ./broken.js 1:3 Module parse failed: Unexpected token (1:3) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders @@ -527,9 +527,548 @@ You may need an appropriate loader to handle this file type, currently no loader webpack compiled with 1 error" `; -exports[`logging should logging warnings in multi-compiler mode: stderr 1`] = `""`; +exports[`logging plugin should logging warnings in multi-compiler mode: stderr 1`] = `""`; -exports[`logging should logging warnings in multi-compiler mode: stdout 1`] = ` +exports[`logging plugin should logging warnings in multi-compiler mode: stdout 1`] = ` +"WARNING in Warning + +webpack compiled with 1 warning + +WARNING in Warning + +webpack compiled with 1 warning" +`; + +exports[`logging standalone should logging an error in "watch" method: stderr 1`] = `"Error: Watch error"`; + +exports[`logging standalone should logging an warning: stderr 1`] = `""`; + +exports[`logging standalone should logging an warning: stdout 1`] = ` +"WARNING in Warning + +webpack compiled with 1 warning" +`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #2: stderr 1`] = `""`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #2: stdout 1`] = ` +"broken: +asset bundle.js x KiB [emitted] (name: main) +./broken.js x bytes [built] [code generated] [1 error] + +ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +broken (webpack x.x.x) compiled with 1 error in x ms + +warning: +asset bundle.js x KiB [emitted] (name: main) +./warning.js x bytes [built] [code generated] + +WARNING in Warning + +warning (webpack x.x.x) compiled with 1 warning in x ms + +success: +asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +success (webpack x.x.x) compiled successfully in x ms" +`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #3: stderr 1`] = `""`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #3: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +./broken.js x bytes [built] [code generated] [1 error] + +ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +webpack x.x.x compiled with 1 error in x ms + +asset bundle.js x KiB [emitted] (name: main) +./warning.js x bytes [built] [code generated] + +WARNING in Warning + +webpack x.x.x compiled with 1 warning in x ms + +asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #4: stderr 1`] = `""`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #4: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +./broken.js x bytes [built] [code generated] [1 error] + +ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +webpack x.x.x compiled with 1 error in x ms + +asset bundle.js x KiB [emitted] (name: main) +./warning.js x bytes [built] [code generated] + +WARNING in Warning + +webpack x.x.x compiled with 1 warning in x ms + +asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #5: stderr 1`] = `""`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration #5: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms + +asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration: stderr 1`] = `""`; + +exports[`logging standalone should logging in multi-compiler and respect the "stats" option from configuration: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +./broken.js x bytes [built] [code generated] [1 error] + +ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +webpack x.x.x compiled with 1 error in x ms + +asset bundle.js x KiB [emitted] (name: main) +./warning.js x bytes [built] [code generated] + +WARNING in Warning + +webpack x.x.x compiled with 1 warning in x ms + +asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build and respect colors #2: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect colors #2: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build and respect colors: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect colors: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build and respect the "NO_COLOR" env: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "NO_COLOR" env: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with custom object value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with custom object value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "false" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "false" value: stdout 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "minimal" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "minimal" value: stdout 1`] = ` +"x assets +x modules +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "none" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "none" value: stdout 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "true" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "true" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "verbose" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build and respect the "stats" option from configuration with the "verbose" value: stdout 1`] = ` +"PublicPath: auto +asset bundle.js x KiB {main} [emitted] (name: main) +asset svg.svg x KiB ({main}) [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes ({main}) [emitted] [from: index.html] (auxiliary name: main) +Entrypoint main x KiB (x KiB) = bundle.js 2 auxiliary assets +chunk {main} (runtime: main) bundle.js (xxxx) x bytes (xxxx) x KiB (xxxx) [entry] [rendered] +> ./foo.js main +runtime modules x KiB +webpack/runtime/define property getters x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/global x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/hasOwnProperty shorthand x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/make namespace object x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/publicPath x KiB {main} [code generated] +[no exports] +[used exports unknown] +cacheable modules x bytes +./foo.js x bytes {main} [depth 0] [built] [code generated] +[used exports unknown] +entry ./foo.js main +./index.html x bytes {main} [depth 1] [dependent] [built] [code generated] +[exports: default] +[used exports unknown] +cjs require ./index.html [./foo.js] 4:0-23 +./svg.svg x bytes {main} [depth 1] [dependent] [built] [code generated] +[exports: default] +[used exports unknown] +cjs require ./svg.svg [./foo.js] 3:0-20 + + +LOG from xxx" +`; + +exports[`logging standalone should logging on successfully build in multi-compiler mode: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build in multi-compiler mode: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms + +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with object value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with object value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "none" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "none" value: stdout 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "normal" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "true" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "true" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "verbose" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the "verbose" value: stdout 1`] = ` +"PublicPath: auto +asset bundle.js x KiB {main} [emitted] (name: main) +asset svg.svg x KiB ({main}) [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes ({main}) [emitted] [from: index.html] (auxiliary name: main) +Entrypoint main x KiB (x KiB) = bundle.js 2 auxiliary assets +chunk {main} (runtime: main) bundle.js (xxxx) x bytes (xxxx) x KiB (xxxx) [entry] [rendered] +> ./foo.js main +runtime modules x KiB +webpack/runtime/define property getters x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/global x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/hasOwnProperty shorthand x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/make namespace object x bytes {main} [code generated] +[no exports] +[used exports unknown] +webpack/runtime/publicPath x KiB {main} [code generated] +[no exports] +[used exports unknown] +cacheable modules x bytes +./foo.js x bytes {main} [depth 0] [built] [code generated] +[used exports unknown] +entry ./foo.js main +./index.html x bytes {main} [depth 1] [dependent] [built] [code generated] +[exports: default] +[used exports unknown] +cjs require ./index.html [./foo.js] 4:0-23 +./svg.svg x bytes {main} [depth 1] [dependent] [built] [code generated] +[exports: default] +[used exports unknown] +cjs require ./svg.svg [./foo.js] 3:0-20 + + +LOG from xxx" +`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the object value and colors: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build using the "stats" option for middleware with the object value and colors: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +`; + +exports[`logging standalone should logging on successfully build when the 'stats' doesn't exist: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build when the 'stats' doesn't exist: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully build: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully build: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) + +asset bundle.js x KiB [emitted] (name: main)" +`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) + +asset bundle.js x KiB [emitted] (name: main)" +`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms + +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms + +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the object value: stderr 1`] = `""`; + +exports[`logging standalone should logging on successfully multi-compiler build using the "stats" option for middleware with the object value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) + +asset bundle.js x KiB [emitted] (name: main)" +`; + +exports[`logging standalone should logging on unsuccessful build in multi-compiler: stderr 1`] = `""`; + +exports[`logging standalone should logging on unsuccessful build in multi-compiler: stdout 1`] = ` +"ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +webpack compiled with 1 error + +ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +webpack compiled with 1 error" +`; + +exports[`logging standalone should logging on unsuccessful build: stderr 1`] = `""`; + +exports[`logging standalone should logging on unsuccessful build: stdout 1`] = ` +"ERROR in ./broken.js 1:3 +Module parse failed: Unexpected token (1:3) +You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders +> 1()2()3() +| + +webpack compiled with 1 error" +`; + +exports[`logging standalone should logging warnings in multi-compiler mode: stderr 1`] = `""`; + +exports[`logging standalone should logging warnings in multi-compiler mode: stdout 1`] = ` "WARNING in Warning webpack compiled with 1 warning diff --git a/test/helpers/runner.js b/test/helpers/runner.js index 13fba0cae..3e34571a7 100755 --- a/test/helpers/runner.js +++ b/test/helpers/runner.js @@ -10,6 +10,8 @@ const defaultConfig = require("../fixtures/webpack.config"); const configEntries = []; const configMiddlewareEntries = []; +const isPlugin = process.argv.includes("--plugin"); + /** * @param {string} NSKey NSKey * @param {string[]} accumulator accumulator @@ -89,21 +91,6 @@ if (Array.isArray(config)) { config.parallelism = 1; } -const compiler = webpack(config); - -if (process.env.WEBPACK_BREAK_WATCH) { - compiler.watch = function watch() { - const error = new Error("Watch error"); - error.code = "watch error"; - - throw error; - }; -} - -compiler.hooks.done.tap("plugin-test", () => { - process.stdout.write("compiled-for-tests"); -}); - switch (process.env.WEBPACK_DEV_MIDDLEWARE_STATS) { case "object": configMiddleware.stats = { all: false, assets: true }; @@ -118,39 +105,112 @@ switch (process.env.WEBPACK_DEV_MIDDLEWARE_STATS) { // Nothing } -const instance = middleware(compiler, configMiddleware); -const app = express(); +let commands = []; +let incompleteCommand = ""; + +const handleStdin = (chunk) => { + const entries = chunk.toString().split("|"); + + incompleteCommand += entries.shift(); + commands.push(incompleteCommand); + incompleteCommand = entries.pop(); + commands = [...commands, ...entries]; + + while (commands.length > 0) { + switch (commands.shift()) { + // case 'invalidate': + // stdinInput = ''; + // instance.waitUntilValid(() => { + // instance.invalidate(); + // }); + // break; + case "exit": + // eslint-disable-next-line n/no-process-exit + process.exit(); + break; + } + } +}; -app.use(instance); -app.listen((error) => { - if (error) { - throw error; +/** + * @param {import("webpack").StatsOptions} statsOptions stats options + * @returns {{ preset: string }} normalized stats + */ +function normalizeStatsOptions(statsOptions) { + if (typeof statsOptions === "undefined") { + statsOptions = { preset: "normal" }; + } else if (typeof statsOptions === "boolean") { + statsOptions = statsOptions ? { preset: "normal" } : { preset: "none" }; + } else if (typeof statsOptions === "string") { + statsOptions = { preset: statsOptions }; } - let commands = []; - let incompleteCommand = ""; - - process.stdin.on("data", (chunk) => { - const entries = chunk.toString().split("|"); - - incompleteCommand += entries.shift(); - commands.push(incompleteCommand); - incompleteCommand = entries.pop(); - commands = [...commands, ...entries]; - - while (commands.length > 0) { - switch (commands.shift()) { - // case 'invalidate': - // stdinInput = ''; - // instance.waitUntilValid(() => { - // instance.invalidate(); - // }); - // break; - case "exit": - // eslint-disable-next-line n/no-process-exit - process.exit(); - break; - } + return statsOptions; +} + +if (isPlugin) { + if (!config.plugins) config.plugins = []; + + config.plugins.push({ + apply(compiler) { + let app; + + compiler.hooks.done.tap("webpack-dev-middleware-test", () => { + const instance = middleware(compiler, configMiddleware); + + app = express(); + app.use(instance); + app.listen((error) => { + if (error) { + throw error; + } + + process.stdin.on("data", handleStdin); + }); + }); + }, + }); + + const compiler = webpack(config); + + compiler.watch({}, (err, stats) => { + if (err) { + throw err; + } + + const statsOptions = normalizeStatsOptions(config.stats); + + if (typeof statsOptions.colors === "undefined") { + statsOptions.colors = compiler.webpack.cli.isColorSupported(); + } + + process.stdout.write("compiled-for-tests"); + process.stdout.write(stats.toString(statsOptions)); + }); +} else { + const compiler = webpack(config); + + if (process.env.WEBPACK_BREAK_WATCH) { + compiler.watch = function watch() { + const error = new Error("Watch error"); + error.code = "watch error"; + + throw error; + }; + } + + compiler.hooks.done.tap("plugin-test", () => { + process.stdout.write("compiled-for-tests"); + }); + const instance = middleware(compiler, configMiddleware); + const app = express(); + + app.use(instance); + app.listen((error) => { + if (error) { + throw error; } + + process.stdin.on("data", handleStdin); }); -}); +} diff --git a/test/logging.test.js b/test/logging.test.js index c80fb32db..b2cecc752 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -65,9 +65,20 @@ function stderrToSnapshot(stderr) { const runner = path.resolve(__dirname, "./helpers/runner.js"); -describe("logging", () => { +const scenarios = [ + { + name: "standalone", + args: [], + }, + { + name: "plugin", + args: ["--plugin"], + }, +]; + +describe.each(scenarios)("logging $name", ({ args }) => { it("should logging on successfully build", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", From a4db2085cb8a70303701ca9dfcf6fd5ad3b28639 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 12:48:06 +0300 Subject: [PATCH 09/15] refactor: code --- src/index.js | 90 +++--------------------------------------- test/helpers/runner.js | 2 +- 2 files changed, 7 insertions(+), 85 deletions(-) diff --git a/src/index.js b/src/index.js index 6651d83dc..d13505942 100644 --- a/src/index.js +++ b/src/index.js @@ -323,10 +323,10 @@ function wdm(compiler, options = {}, isPlugin = false) { /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal - * @param {boolean} usePlugin whether to use as webpack plugin + * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false * @returns {HapiPlugin} hapi wrapper */ -function createHapiWrapper(usePlugin = false) { +function hapiWrapper(usePlugin = false) { return { pkg: { name: "webpack-dev-middleware", @@ -397,36 +397,17 @@ function createHapiWrapper(usePlugin = false) { }; } -/** - * @template HapiServer - * @template {HapiOptions} HapiOptionsInternal - * @returns {HapiPlugin} hapi wrapper - */ -function hapiWrapper() { - return createHapiWrapper(false); -} - -/** - * @template HapiServer - * @template {HapiOptions} HapiOptionsInternal - * @returns {HapiPlugin} hapi plugin wrapper - */ -function hapiPluginWrapper() { - return createHapiWrapper(true); -} - wdm.hapiWrapper = hapiWrapper; -wdm.hapiPluginWrapper = hapiPluginWrapper; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options - * @param {boolean} usePlugin whether to use as webpack plugin + * @param {boolean=} usePlugin whether to use as webpack plugin * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper */ -function createKoaWrapper(compiler, options, usePlugin = false) { +function koaWrapper(compiler, options, usePlugin) { const devMiddleware = wdm(compiler, options, usePlugin); if (usePlugin) { @@ -542,40 +523,17 @@ function createKoaWrapper(compiler, options, usePlugin = false) { return webpackDevMiddleware; } -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper - */ -function koaWrapper(compiler, options) { - return createKoaWrapper(compiler, options, false); -} - -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow plugin wrapper - */ -function koaPluginWrapper(compiler, options) { - return createKoaWrapper(compiler, options, true); -} - wdm.koaWrapper = koaWrapper; -wdm.koaPluginWrapper = koaPluginWrapper; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options - * @param {boolean} usePlugin whether to use as webpack plugin + * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono wrapper */ -function createHonoWrapper(compiler, options, usePlugin = false) { +function honoWrapper(compiler, options, usePlugin) { const devMiddleware = wdm(compiler, options, usePlugin); /** @@ -743,42 +701,6 @@ function createHonoWrapper(compiler, options, usePlugin = false) { return webpackDevMiddleware; } -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono wrapper - */ -function honoWrapper(compiler, options) { - return createHonoWrapper(compiler, options, false); -} - -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono plugin wrapper - */ -function honoPluginWrapper(compiler, options) { - return createHonoWrapper(compiler, options, true); -} - wdm.honoWrapper = honoWrapper; -wdm.honoPluginWrapper = honoPluginWrapper; - -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {API} webpack dev middleware - */ -function plugin(compiler, options = {}) { - return wdm(compiler, options, true); -} - -wdm.plugin = plugin; module.exports = wdm; diff --git a/test/helpers/runner.js b/test/helpers/runner.js index 3e34571a7..b9c82f2e5 100755 --- a/test/helpers/runner.js +++ b/test/helpers/runner.js @@ -156,7 +156,7 @@ if (isPlugin) { let app; compiler.hooks.done.tap("webpack-dev-middleware-test", () => { - const instance = middleware(compiler, configMiddleware); + const instance = middleware(compiler, configMiddleware, true); app = express(); app.use(instance); From 8ce49f3a0b4e86591b15bd6fadc04c7402d763a3 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 12:55:55 +0300 Subject: [PATCH 10/15] refactor: code --- README.md | 26 +++++++----- src/index.js | 13 +----- src/utils/setupHooks.js | 5 ++- test/logging.test.js | 72 ++++++++++++++++---------------- types/index.d.ts | 83 ++++++++++--------------------------- types/utils/setupHooks.d.ts | 2 + 6 files changed, 79 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 46b8a83b9..12823b4d5 100644 --- a/README.md +++ b/README.md @@ -492,9 +492,13 @@ const compiler = webpack({ plugins: [ { apply(compiler) { - const devMiddleware = middleware.plugin(compiler, { - /* webpack-dev-middleware options */ - }); + const devMiddleware = middleware( + compiler, + { + /* webpack-dev-middleware options */ + }, + true, + ); }, }, ], @@ -515,12 +519,12 @@ compiler.watch((err, stats) => { The following wrappers enable plugin mode for framework integrations: -- `middleware.koaPluginWrapper(compiler, options)` -- `middleware.hapiPluginWrapper()` -- `middleware.honoPluginWrapper(compiler, options)` +- `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. +They are equivalent to `koaWrapper`/`hapiWrapper`/`honoWrapper`, but use plugin mode logging behavior. ### `forwardError` @@ -768,7 +772,7 @@ 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.koaPluginWrapper(compiler, devMiddlewareOptions)); +// app.use(middleware.koaWrapper(compiler, devMiddlewareOptions, true)); app.listen(3000); ``` @@ -797,7 +801,7 @@ await server.register({ // 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.hapiPluginWrapper(), +// plugin: devMiddleware.hapiWrapper(true), // options: { // // The `compiler` option is required // compiler, @@ -854,7 +858,7 @@ 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.honoPluginWrapper(compiler, devMiddlewareOptions) +// const honoDevMiddleware = devMiddleware.honoWrapper(compiler, devMiddlewareOptions, true) serve(app); ``` diff --git a/src/index.js b/src/index.js index d13505942..0abc8f794 100644 --- a/src/index.js +++ b/src/index.js @@ -90,7 +90,6 @@ const noop = () => {}; * @property {Watching | MultiWatching | undefined} watching watching * @property {Logger} logger logger * @property {OutputFileSystem} outputFileSystem output file system - * @property {boolean} isPlugin whether wdm is used as webpack plugin */ /** @@ -226,10 +225,9 @@ function wdm(compiler, options = {}, isPlugin = false) { options, compiler, logger: compiler.getInfrastructureLogger("webpack-dev-middleware"), - isPlugin: false, }; - setupHooks(context); + setupHooks(context, isPlugin); if (options.writeToDisk) { setupWriteToDisk(context); @@ -342,11 +340,6 @@ function hapiWrapper(usePlugin = false) { const devMiddleware = wdm(compiler, rest, usePlugin); - if (usePlugin) { - // Use logger when used as webpack plugin - devMiddleware.context.isPlugin = true; - } - // @ts-expect-error if (!server.decorations.server.includes("webpackDevMiddleware")) { // @ts-expect-error @@ -410,10 +403,6 @@ wdm.hapiWrapper = hapiWrapper; function koaWrapper(compiler, options, usePlugin) { const devMiddleware = wdm(compiler, options, usePlugin); - if (usePlugin) { - devMiddleware.context.isPlugin = true; - } - /** * @param {{ req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | { message: string }, state: object }} ctx context * @param {EXPECTED_FUNCTION} next next diff --git a/src/utils/setupHooks.js b/src/utils/setupHooks.js index d51c8bf8f..e09638023 100644 --- a/src/utils/setupHooks.js +++ b/src/utils/setupHooks.js @@ -14,8 +14,9 @@ * @template {IncomingMessage} Request * @template {ServerResponse} Response * @param {import("../index.js").WithOptional, "watching" | "outputFileSystem">} context context + * @param {boolean=} isPlugin true when it is a plugin usage, otherwise false */ -function setupHooks(context) { +function setupHooks(context, isPlugin) { /** * @returns {void} */ @@ -66,7 +67,7 @@ function setupHooks(context) { } // For plugin support we should print nothing, because webpack/webpack-cli/webpack-dev-server will print them on using `stats.toString()` - if (!context.isPlugin) { + if (!isPlugin) { logger.log("Compilation finished"); const isMultiCompilerMode = Boolean( diff --git a/test/logging.test.js b/test/logging.test.js index b2cecc752..718c5757d 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -116,7 +116,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging on successfully build and respect colors", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-colors-true.config.js", @@ -154,7 +154,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging on successfully build and respect colors #2", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-colors-false.config.js", @@ -192,7 +192,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging on successfully build when the 'stats' doesn't exist", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.no-stats.config.js", @@ -230,7 +230,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "stats" option from configuration with the "none" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-none.config.js", @@ -266,7 +266,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "stats" option from configuration with the "minimal" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-minimal.config", @@ -304,7 +304,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "stats" option from configuration with the "verbose" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-verbose.config", @@ -342,7 +342,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "stats" option from configuration with the "true" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-true.config", @@ -380,7 +380,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "stats" option from configuration with the "false" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-false.config", @@ -416,7 +416,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "stats" option from configuration with custom object value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.stats-object.config", @@ -454,7 +454,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging on successfully build in multi-compiler mode", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -492,7 +492,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging on unsuccessful build", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.error.config", @@ -530,7 +530,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging on unsuccessful build in multi-compiler", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.error.config", @@ -568,7 +568,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging an warning", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.warning.config", @@ -606,7 +606,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it("should logging warnings in multi-compiler mode", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.warning.config", @@ -644,7 +644,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging in multi-compiler and respect the "stats" option from configuration', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.one-error-one-warning-one-success", @@ -682,7 +682,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging in multi-compiler and respect the "stats" option from configuration #2', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: @@ -721,7 +721,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging in multi-compiler and respect the "stats" option from configuration #3', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.one-error-one-warning-one-no", @@ -759,7 +759,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging in multi-compiler and respect the "stats" option from configuration #4', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.one-error-one-warning-one-object", @@ -797,7 +797,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging in multi-compiler and respect the "stats" option from configuration #5', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.dev-server-false", @@ -835,7 +835,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging an error in "watch" method', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_BREAK_WATCH: true, @@ -870,7 +870,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { fs.mkdirSync(outputDir, { recursive: true }); fs.chmodSync(outputDir, 0o400); - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.simple.config", @@ -904,7 +904,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { } it('should logging on successfully build using the "stats" option for middleware with the "true" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -943,7 +943,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with the "false" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -981,7 +981,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with the "none" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -1019,7 +1019,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with the "normal" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -1058,7 +1058,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with the "verbose" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -1097,7 +1097,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with object value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -1136,7 +1136,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with the object value and colors', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -1175,7 +1175,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build using the "stats" option for middleware with object value and no colors', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", @@ -1214,7 +1214,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully multi-compiler build using the "stats" option for middleware with the "true" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -1253,7 +1253,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -1291,7 +1291,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -1330,7 +1330,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully multi-compiler build using the "stats" option for middleware with the object value', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -1369,7 +1369,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully multi-compiler build using the "stats" option for middleware with object value and colors', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -1408,7 +1408,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.array.config", @@ -1447,7 +1447,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { }); it('should logging on successfully build and respect the "NO_COLOR" env', (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.config", diff --git a/types/index.d.ts b/types/index.d.ts index 213523f46..df53752f7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -63,7 +63,6 @@ export = wdm; * @property {Watching | MultiWatching | undefined} watching watching * @property {Logger} logger logger * @property {OutputFileSystem} outputFileSystem output file system - * @property {boolean} isPlugin whether wdm is used as webpack plugin */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] @@ -154,6 +153,7 @@ export = wdm; * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options + * @param {boolean} isPlugin true when will use as a plugin, otherwise false * @returns {API} webpack dev middleware */ declare function wdm< @@ -162,16 +162,13 @@ declare function wdm< >( compiler: Compiler | MultiCompiler, options?: Options | undefined, + isPlugin?: boolean, ): API; declare namespace wdm { export { hapiWrapper, - hapiPluginWrapper, koaWrapper, - koaPluginWrapper, honoWrapper, - honoPluginWrapper, - plugin, Schema, Compiler, MultiCompiler, @@ -214,28 +211,35 @@ declare namespace wdm { }; } /** - * @template HapiServer - * @template {HapiOptions} HapiOptionsInternal - * @returns {HapiPlugin} hapi wrapper + * @template S + * @template O + * @typedef {object} HapiPluginBase + * @property {(server: S, options: O) => void | Promise} register register + */ +/** + * @template S + * @template O + * @typedef {HapiPluginBase & { pkg: { name: string }, multiple: boolean }} HapiPlugin + */ +/** + * @typedef {Options & { compiler: Compiler | MultiCompiler }} HapiOptions */ -declare function hapiWrapper< - HapiServer, - HapiOptionsInternal extends HapiOptions, ->(): HapiPlugin; /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal - * @returns {HapiPlugin} hapi plugin wrapper + * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false + * @returns {HapiPlugin} hapi wrapper */ -declare function hapiPluginWrapper< +declare function hapiWrapper< HapiServer, HapiOptionsInternal extends HapiOptions, ->(): HapiPlugin; +>(usePlugin?: boolean | undefined): HapiPlugin; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @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 */ declare function koaWrapper< @@ -244,26 +248,14 @@ declare function koaWrapper< >( compiler: Compiler | MultiCompiler, options?: Options | undefined, + usePlugin?: boolean | undefined, ): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options=} options options - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow plugin wrapper - */ -declare function koaPluginWrapper< - RequestInternal extends IncomingMessage = import("http").IncomingMessage, - ResponseInternal extends ServerResponse = ServerResponse, ->( - compiler: Compiler | MultiCompiler, - options?: Options | undefined, -): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; -/** - * @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 */ declare function honoWrapper< @@ -272,35 +264,8 @@ declare function honoWrapper< >( compiler: Compiler | MultiCompiler, options?: Options | undefined, + usePlugin?: boolean | undefined, ): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} hono plugin wrapper - */ -declare function honoPluginWrapper< - RequestInternal extends IncomingMessage = import("http").IncomingMessage, - ResponseInternal extends ServerResponse = ServerResponse, ->( - compiler: Compiler | MultiCompiler, - options?: Options | undefined, -): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; -/** - * @template {IncomingMessage} [RequestInternal=IncomingMessage] - * @template {ServerResponse} [ResponseInternal=ServerResponse] - * @param {Compiler | MultiCompiler} compiler compiler - * @param {Options=} options options - * @returns {API} webpack dev middleware - */ -declare function plugin< - RequestInternal extends IncomingMessage = import("http").IncomingMessage, - ResponseInternal extends ServerResponse = ServerResponse, ->( - compiler: Compiler | MultiCompiler, - options?: Options | undefined, -): API; type Schema = import("schema-utils/declarations/validate").Schema; type Compiler = import("webpack").Compiler; type MultiCompiler = import("webpack").MultiCompiler; @@ -390,10 +355,6 @@ type Context< * output file system */ outputFileSystem: OutputFileSystem; - /** - * whether wdm is used as webpack plugin - */ - isPlugin: boolean; }; type FilledContext< RequestInternal extends IncomingMessage = import("http").IncomingMessage, diff --git a/types/utils/setupHooks.d.ts b/types/utils/setupHooks.d.ts index 1ec0e489b..512582e48 100644 --- a/types/utils/setupHooks.d.ts +++ b/types/utils/setupHooks.d.ts @@ -13,6 +13,7 @@ export = setupHooks; * @template {IncomingMessage} Request * @template {ServerResponse} Response * @param {import("../index.js").WithOptional, "watching" | "outputFileSystem">} context context + * @param {boolean=} isPlugin true when it is a plugin usage, otherwise false */ declare function setupHooks< Request extends IncomingMessage, @@ -22,6 +23,7 @@ declare function setupHooks< import("../index.js").Context, "watching" | "outputFileSystem" >, + isPlugin?: boolean | undefined, ): void; declare namespace setupHooks { export { From 05745390c821ec949100916d841e397447eca612 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 14:04:37 +0300 Subject: [PATCH 11/15] test: fix --- README.md | 7 +- .../logging.test.js.snap.webpack5 | 139 ++++++++++++------ test/helpers/runner.js | 48 +++++- 3 files changed, 140 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 12823b4d5..823f108fa 100644 --- a/README.md +++ b/README.md @@ -481,8 +481,11 @@ instance.waitUntilValid(() => { 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)`) -(`compiler.getInfrastructureLogger`) instead of `console.log`. +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"); diff --git a/test/__snapshots__/logging.test.js.snap.webpack5 b/test/__snapshots__/logging.test.js.snap.webpack5 index b931e29dc..1bc9bec7a 100644 --- a/test/__snapshots__/logging.test.js.snap.webpack5 +++ b/test/__snapshots__/logging.test.js.snap.webpack5 @@ -299,7 +299,13 @@ exports[`logging plugin should logging on successfully build using the "stats" o exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value and no colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) -asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value: stderr 1`] = `""`; @@ -307,16 +313,42 @@ exports[`logging plugin should logging on successfully build using the "stats" o exports[`logging plugin should logging on successfully build using the "stats" option for middleware with object value: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) -asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; -exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "false" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "none" value: stderr 1`] = `""`; -exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "none" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "none" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; @@ -349,44 +381,15 @@ webpack x.x.x compiled successfully in x ms" exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "verbose" value: stderr 1`] = `""`; exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "verbose" value: stdout 1`] = ` -"PublicPath: auto -asset bundle.js x KiB {main} [emitted] (name: main) -asset svg.svg x KiB ({main}) [emitted] [from: svg.svg] (auxiliary name: main) -asset index.html x bytes ({main}) [emitted] [from: index.html] (auxiliary name: main) -Entrypoint main x KiB (x KiB) = bundle.js 2 auxiliary assets -chunk {main} (runtime: main) bundle.js (xxxx) x bytes (xxxx) x KiB (xxxx) [entry] [rendered] -> ./foo.js main -runtime modules x KiB -webpack/runtime/define property getters x bytes {main} [code generated] -[no exports] -[used exports unknown] -webpack/runtime/global x bytes {main} [code generated] -[no exports] -[used exports unknown] -webpack/runtime/hasOwnProperty shorthand x bytes {main} [code generated] -[no exports] -[used exports unknown] -webpack/runtime/make namespace object x bytes {main} [code generated] -[no exports] -[used exports unknown] -webpack/runtime/publicPath x KiB {main} [code generated] -[no exports] -[used exports unknown] +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules cacheable modules x bytes -./foo.js x bytes {main} [depth 0] [built] [code generated] -[used exports unknown] -entry ./foo.js main -./index.html x bytes {main} [depth 1] [dependent] [built] [code generated] -[exports: default] -[used exports unknown] -cjs require ./index.html [./foo.js] 4:0-23 -./svg.svg x bytes {main} [depth 1] [dependent] [built] [code generated] -[exports: default] -[used exports unknown] -cjs require ./svg.svg [./foo.js] 3:0-20 - - -LOG from xxx" +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the object value and colors: stderr 1`] = `""`; @@ -394,7 +397,13 @@ exports[`logging plugin should logging on successfully build using the "stats" o exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the object value and colors: stdout 1`] = ` "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) -asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main)" +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x KiB x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on successfully build when the 'stats' doesn't exist: stderr 1`] = `""`; @@ -431,8 +440,16 @@ exports[`logging plugin should logging on successfully multi-compiler build usin "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms -asset bundle.js x KiB [emitted] (name: main)" +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with object value and no colors: stderr 1`] = `""`; @@ -441,13 +458,35 @@ exports[`logging plugin should logging on successfully multi-compiler build usin "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms -asset bundle.js x KiB [emitted] (name: main)" +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stderr 1`] = `""`; -exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stdout 1`] = `""`; +exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "false" value: stdout 1`] = ` +"asset bundle.js x KiB [emitted] (name: main) +asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) +asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms + +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" +`; exports[`logging plugin should logging on successfully multi-compiler build using the "stats" option for middleware with the "normal" value: stderr 1`] = `""`; @@ -491,8 +530,16 @@ exports[`logging plugin should logging on successfully multi-compiler build usin "asset bundle.js x KiB [emitted] (name: main) asset svg.svg x KiB [emitted] [from: svg.svg] (auxiliary name: main) asset index.html x bytes [emitted] [from: index.html] (auxiliary name: main) +runtime modules x bytes x modules +cacheable modules x bytes +./foo.js x bytes [built] [code generated] +./svg.svg x bytes [built] [code generated] +./index.html x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms -asset bundle.js x KiB [emitted] (name: main)" +asset bundle.js x KiB [emitted] (name: main) +./bar.js x bytes [built] [code generated] +webpack x.x.x compiled successfully in x ms" `; exports[`logging plugin should logging on unsuccessful build in multi-compiler: stderr 1`] = `""`; diff --git a/test/helpers/runner.js b/test/helpers/runner.js index b9c82f2e5..a21554efb 100755 --- a/test/helpers/runner.js +++ b/test/helpers/runner.js @@ -133,10 +133,11 @@ const handleStdin = (chunk) => { }; /** + * @param {import("webpack").Compiler} compiler compiler * @param {import("webpack").StatsOptions} statsOptions stats options * @returns {{ preset: string }} normalized stats */ -function normalizeStatsOptions(statsOptions) { +function normalizeStatsOptions(compiler, statsOptions) { if (typeof statsOptions === "undefined") { statsOptions = { preset: "normal" }; } else if (typeof statsOptions === "boolean") { @@ -145,10 +146,28 @@ function normalizeStatsOptions(statsOptions) { statsOptions = { preset: statsOptions }; } + if (typeof statsOptions.colors === "undefined") { + statsOptions.colors = compiler.webpack.cli.isColorSupported(); + } + + // Just for test, in the real world webpack-dev-middleware doesn't support stats options as a plugin + switch (process.env.WEBPACK_DEV_MIDDLEWARE_STATS) { + case "object_colors_true": + statsOptions.colors = true; + break; + case "object_colors_false": + statsOptions.colors = false; + break; + } + return statsOptions; } -if (isPlugin) { +/** + * @param {import("webpack").Configuration} config configuration + * @returns {import("webpack").Configuration} configuration with the test plugin + */ +function addPlugin(config) { if (!config.plugins) config.plugins = []; config.plugins.push({ @@ -171,19 +190,36 @@ if (isPlugin) { }, }); - const compiler = webpack(config); + return config; +} + +if (isPlugin) { + const isMultiCompiler = Array.isArray(config); + + const compiler = webpack( + isMultiCompiler ? config.map((item) => addPlugin(item)) : addPlugin(config), + ); compiler.watch({}, (err, stats) => { if (err) { throw err; } - const statsOptions = normalizeStatsOptions(config.stats); + if (process.env.WEBPACK_BREAK_WATCH) { + const error = new Error("Watch error"); + error.code = "watch error"; - if (typeof statsOptions.colors === "undefined") { - statsOptions.colors = compiler.webpack.cli.isColorSupported(); + throw error; } + const statsOptions = isMultiCompiler + ? { + children: config.map((config, idx) => + normalizeStatsOptions(compiler.compilers[idx], config.stats), + ), + } + : normalizeStatsOptions(compiler, config.stats); + process.stdout.write("compiled-for-tests"); process.stdout.write(stats.toString(statsOptions)); }); From dc54244e3dcab999be24dba05f539d87d1a28b0b Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 14:42:48 +0300 Subject: [PATCH 12/15] test: stability --- src/middleware.js | 1 + .../webpack.array.dev-server-false.js | 4 +- test/middleware.test.js | 56 ++++++++++--------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/middleware.js b/src/middleware.js index 946d4d73f..063f672ab 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -876,6 +876,7 @@ function wrapper(context) { // Error handling /** @type {import("fs").ReadStream} */ (bufferOrStream).on("error", (error) => { + context.logger.error("Stream error:", error); // clean up stream early cleanup(); errorHandler(error); diff --git a/test/fixtures/webpack.array.dev-server-false.js b/test/fixtures/webpack.array.dev-server-false.js index e23d44a2b..4687ecfea 100644 --- a/test/fixtures/webpack.array.dev-server-false.js +++ b/test/fixtures/webpack.array.dev-server-false.js @@ -10,7 +10,7 @@ module.exports = [ output: { filename: 'bundle.js', path: path.resolve(__dirname, '../outputs/dev-server-false/js3'), - publicPath: '/static-two/', + publicPath: '/static-one/', }, infrastructureLogging: { level: 'none' @@ -25,7 +25,7 @@ module.exports = [ output: { filename: 'bundle.js', path: path.resolve(__dirname, '../outputs/dev-server-false/js4'), - publicPath: '/static-one/', + publicPath: '/static-two/', }, module: { rules: [ diff --git a/test/middleware.test.js b/test/middleware.test.js index 0ad0a2740..5182a3cb0 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -1450,19 +1450,20 @@ describe.each([ await close(server, instance); }); - it('should return "200" code for GET request to the bundle file for the first compiler', async () => { + it('should return "404" code for GET request to the bundle file for the first compiler', async () => { const bundlePath = path.resolve( __dirname, - "./outputs/dev-server-false/js4/", - ); - - expect(fs.existsSync(path.resolve(bundlePath, "bundle.js"))).toBe( - false, + "./outputs/dev-server-false/js3/", ); const response = await req.get("/static-one/bundle.js"); - expect(response.statusCode).toBe(200); + expect(response.statusCode).toBe(404); + + // Stored in the real fs + expect(fs.existsSync(path.resolve(bundlePath, "bundle.js"))).toBe( + true, + ); }); it('should return "404" code for GET request to a non existing file for the first compiler', async () => { @@ -1471,37 +1472,32 @@ describe.each([ expect(response.statusCode).toBe(404); }); - it('should return "200" code for GET request to the "public" path for the first compiler', async () => { + it('should return "404" code for GET request to the "public" path for the first compiler', async () => { const response = await req.get("/static-one/"); - expect(response.statusCode).toBe(200); - expect(response.headers["content-type"]).toBe( - "text/html; charset=utf-8", - ); + expect(response.statusCode).toBe(404); }); - it('should return "200" code for GET request to the "index" option for the first compiler', async () => { + it('should return "404" code for GET request to the "index" option for the first compiler', async () => { const response = await req.get("/static-one/index.html"); - expect(response.statusCode).toBe(200); - expect(response.headers["content-type"]).toBe( - "text/html; charset=utf-8", - ); + expect(response.statusCode).toBe(404); }); it('should return "200" code for GET request for the bundle file for the second compiler', async () => { const bundlePath = path.resolve( __dirname, - "./outputs/dev-server-false/js3/", - ); - - expect(fs.existsSync(path.resolve(bundlePath, "bundle.js"))).toBe( - true, + "./outputs/dev-server-false/js4/", ); const response = await req.get("/static-two/bundle.js"); - expect(response.statusCode).toBe(404); + expect(response.statusCode).toBe(200); + + // stored in memory + expect(fs.existsSync(path.resolve(bundlePath, "bundle.js"))).toBe( + false, + ); }); it('should return "404" code for GET request to a non existing file for the second compiler', async () => { @@ -1510,16 +1506,22 @@ describe.each([ expect(response.statusCode).toBe(404); }); - it('should return "404" code for GET request to the "public" path for the second compiler', async () => { + it('should return "200" code for GET request to the "public" path for the second compiler', async () => { const response = await req.get("/static-two/"); - expect(response.statusCode).toBe(404); + expect(response.statusCode).toBe(200); + expect(response.headers["content-type"]).toBe( + "text/html; charset=utf-8", + ); }); - it('should return "404" code for GET request to the "index" option for the second compiler', async () => { + it('should return "200" code for GET request to the "index" option for the second compiler', async () => { const response = await req.get("/static-two/index.html"); - expect(response.statusCode).toBe(404); + expect(response.statusCode).toBe(200); + expect(response.headers["content-type"]).toBe( + "text/html; charset=utf-8", + ); }); it('should return "404" code for GET request to the non-public path', async () => { From f9a19c59354ea8268d798bc09d0d83b57518152c Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 15:01:42 +0300 Subject: [PATCH 13/15] test: stability --- .../webpack.array.dev-server-false-logging.js | 44 +++++++++++++++++++ test/fixtures/webpack.array.logging.config.js | 43 ++++++++++++++++++ test/logging.test.js | 16 +++---- test/middleware.test.js | 28 +++++------- 4 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/webpack.array.dev-server-false-logging.js create mode 100644 test/fixtures/webpack.array.logging.config.js diff --git a/test/fixtures/webpack.array.dev-server-false-logging.js b/test/fixtures/webpack.array.dev-server-false-logging.js new file mode 100644 index 000000000..9210eafaa --- /dev/null +++ b/test/fixtures/webpack.array.dev-server-false-logging.js @@ -0,0 +1,44 @@ +'use strict'; + +const path = require('path'); + +module.exports = [ + { + mode: 'development', + context: path.resolve(__dirname), + entry: './bar.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, '../outputs/dev-server-false-logging/js3'), + publicPath: '/static-one/', + }, + infrastructureLogging: { + level: 'none' + }, + stats: 'normal', + devServer: false, + }, + { + mode: 'development', + context: path.resolve(__dirname), + entry: './foo.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, '../outputs/dev-server-false-logging/js4'), + publicPath: '/static-two/', + }, + module: { + rules: [ + { + test: /\.(svg|html)$/, + loader: 'file-loader', + options: { name: '[name].[ext]' }, + }, + ], + }, + infrastructureLogging: { + level: 'none' + }, + stats: 'normal' + } +]; diff --git a/test/fixtures/webpack.array.logging.config.js b/test/fixtures/webpack.array.logging.config.js new file mode 100644 index 000000000..62d8265a6 --- /dev/null +++ b/test/fixtures/webpack.array.logging.config.js @@ -0,0 +1,43 @@ +'use strict'; + +const path = require('path'); + +module.exports = [ + { + mode: 'development', + context: path.resolve(__dirname), + entry: './foo.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, '../outputs/array-logging/js1'), + publicPath: '/static-one/', + }, + module: { + rules: [ + { + test: /\.(svg|html)$/, + loader: 'file-loader', + options: { name: '[name].[ext]' }, + }, + ], + }, + infrastructureLogging: { + level: 'none' + }, + stats: 'normal' + }, + { + mode: 'development', + context: path.resolve(__dirname), + entry: './bar.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, '../outputs/array-logging/js2'), + publicPath: '/static-two/', + }, + infrastructureLogging: { + level: 'none' + }, + stats: 'normal' + }, +]; diff --git a/test/logging.test.js b/test/logging.test.js index 718c5757d..117ef1d32 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -457,7 +457,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", FORCE_COLOR: true, }, }); @@ -800,7 +800,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.dev-server-false", + WEBPACK_CONFIG: "webpack.array.dev-server-false-logging", FORCE_COLOR: true, }, }); @@ -1217,7 +1217,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WMC_stats: true, FORCE_COLOR: true, }, @@ -1256,7 +1256,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WMC_stats: false, FORCE_COLOR: true, }, @@ -1294,7 +1294,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WMC_stats: "normal", FORCE_COLOR: true, }, @@ -1333,7 +1333,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WEBPACK_DEV_MIDDLEWARE_STATS: "object", FORCE_COLOR: true, }, @@ -1372,7 +1372,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WEBPACK_DEV_MIDDLEWARE_STATS: "object_colors_true", FORCE_COLOR: true, }, @@ -1411,7 +1411,7 @@ describe.each(scenarios)("logging $name", ({ args }) => { const proc = execa(runner, args, { stdio: "pipe", env: { - WEBPACK_CONFIG: "webpack.array.config", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WEBPACK_DEV_MIDDLEWARE_STATS: "object_colors_false", FORCE_COLOR: true, }, diff --git a/test/middleware.test.js b/test/middleware.test.js index 5182a3cb0..94f4a1e70 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -3766,23 +3766,17 @@ describe.each([ } }); - // TODO: why koa and hono don't catch for their error handling when stream emit error? - (name === "koa" || name === "hono" ? it.skip : it)( - 'should return the "500" code for the "GET" request to the "image.svg" file when it throws a reading error', - async () => { - const response = await req.get("/image.svg"); - - // eslint-disable-next-line jest/no-standalone-expect - expect(response.statusCode).toBe(500); - if (name !== "hapi") { - // eslint-disable-next-line jest/no-standalone-expect - expect(nextWasCalled).toBe(true); - } else { - // eslint-disable-next-line jest/no-standalone-expect - expect(nextWasCalled).toBe(false); - } - }, - ); + it('should return the "500" code for the "GET" request to the "image.svg" file when it throws a reading error', async () => { + const response = await req.get("/image.svg"); + + expect(response.statusCode).toBe(500); + + if (name !== "hapi") { + expect(nextWasCalled).toBe(true); + } else { + expect(nextWasCalled).toBe(false); + } + }); it('should return the "200" code for the "HEAD" request to the bundle file', async () => { const response = await req.head("/file.text"); From 4ad1a8dc8b59ac77aff36c85a93d53e1aaed834e Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 15:29:54 +0300 Subject: [PATCH 14/15] test: stability --- src/index.js | 12 +++- test/middleware.test.js | 142 +++++++++++++++++++--------------------- 2 files changed, 79 insertions(+), 75 deletions(-) diff --git a/src/index.js b/src/index.js index 0abc8f794..897583705 100644 --- a/src/index.js +++ b/src/index.js @@ -401,6 +401,8 @@ wdm.hapiWrapper = hapiWrapper; * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper */ function koaWrapper(compiler, options, usePlugin) { + const { finished } = require("node:stream"); + const devMiddleware = wdm(compiler, options, usePlugin); /** @@ -445,9 +447,15 @@ function koaWrapper(compiler, options, usePlugin) { res.stream = (stream) => { ctx.body = stream; - isFinished = true; + finished(stream, (err) => { + isFinished = true; - resolve(); + if (err) { + reject(err); + } else { + resolve(); + } + }); }; /** * @param {string | Buffer} data data diff --git a/test/middleware.test.js b/test/middleware.test.js index 94f4a1e70..48edfffc1 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -3771,10 +3771,11 @@ describe.each([ expect(response.statusCode).toBe(500); - if (name !== "hapi") { - expect(nextWasCalled).toBe(true); - } else { + // hapi and hono don't support passthrough errors + if (name === "hapi" || name === "hono") { expect(nextWasCalled).toBe(false); + } else { + expect(nextWasCalled).toBe(true); } }); @@ -4232,87 +4233,83 @@ describe.each([ }); describe("writeToDisk option", () => { - (name === "hono" ? describe.skip : describe)( - 'should work with "true" value', - () => { - let compiler; - - const outputPath = path.resolve( - __dirname, - "./outputs/write-to-disk-true", - ); + describe('should work with "true" value', () => { + let compiler; - beforeAll(async () => { - compiler = getCompiler({ - ...webpackConfig, - output: { - filename: "bundle.js", - path: outputPath, - publicPath: "/public/", - }, - }); + const outputPath = path.resolve( + __dirname, + "./outputs/write-to-disk-true", + ); - [server, req, instance] = await frameworkFactory( - name, - framework, - compiler, - { writeToDisk: true }, - ); + beforeAll(async () => { + compiler = getCompiler({ + ...webpackConfig, + output: { + filename: "bundle.js", + path: outputPath, + publicPath: "/public/", + }, }); - afterAll(async () => { - await fs.promises.rm(outputPath, { - recursive: true, - force: true, - }); - await close(server, instance); + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { writeToDisk: true }, + ); + }); + + afterAll(async () => { + await fs.promises.rm(outputPath, { + recursive: true, + force: true, }); + await close(server, instance); + }); - it("should find the bundle file on disk", (done) => { - req.get("/public/bundle.js").expect(200, (error) => { - if (error) { - return done(error); - } + it("should find the bundle file on disk", (done) => { + req.get("/public/bundle.js").expect(200, (error) => { + if (error) { + return done(error); + } - const bundlePath = path.resolve( - __dirname, - "./outputs/write-to-disk-true/bundle.js", - ); + const bundlePath = path.resolve( + __dirname, + "./outputs/write-to-disk-true/bundle.js", + ); - expect( - compiler.hooks.assetEmitted.taps.filter( - (hook) => hook.name === "DevMiddleware", - ), - ).toHaveLength(1); - expect(fs.existsSync(bundlePath)).toBe(true); + expect( + compiler.hooks.assetEmitted.taps.filter( + (hook) => hook.name === "DevMiddleware", + ), + ).toHaveLength(1); + expect(fs.existsSync(bundlePath)).toBe(true); - instance.invalidate(); + instance.invalidate(); - return compiler.hooks.done.tap( - "DevMiddlewareWriteToDiskTest", - () => { - expect( - compiler.hooks.assetEmitted.taps.filter( - (hook) => hook.name === "DevMiddleware", - ), - ).toHaveLength(1); + return compiler.hooks.done.tap( + "DevMiddlewareWriteToDiskTest", + () => { + expect( + compiler.hooks.assetEmitted.taps.filter( + (hook) => hook.name === "DevMiddleware", + ), + ).toHaveLength(1); - done(); - }, - ); - }); + done(); + }, + ); }); + }); - it("should not allow to get files above root", async () => { - const response = await req.get( - "/public/..%2f../middleware.test.js", - ); + it("should not allow to get files above root", async () => { + const response = await req.get("/public/..%2f../middleware.test.js"); - expect(response.statusCode).toBe(403); - expect(response.headers["content-type"]).toBe( - "text/html; charset=utf-8", - ); - expect(response.text).toBe(` + expect(response.statusCode).toBe(403); + expect(response.headers["content-type"]).toBe( + "text/html; charset=utf-8", + ); + expect(response.text).toBe(` @@ -4322,9 +4319,8 @@ describe.each([
Forbidden
`); - }); - }, - ); + }); + }); describe('should work with "true" value when the `output.clean` is `true`', () => { const outputPath = path.resolve( From 353e92eeaf4864f70dd36ea6bc62cda75d3f9865 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Wed, 11 Mar 2026 16:00:06 +0300 Subject: [PATCH 15/15] refactor: code --- src/index.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/index.js b/src/index.js index 897583705..7a4753e10 100644 --- a/src/index.js +++ b/src/index.js @@ -401,8 +401,6 @@ wdm.hapiWrapper = hapiWrapper; * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void} kow wrapper */ function koaWrapper(compiler, options, usePlugin) { - const { finished } = require("node:stream"); - const devMiddleware = wdm(compiler, options, usePlugin); /** @@ -445,17 +443,32 @@ function koaWrapper(compiler, options, usePlugin) { * @param {import("fs").ReadStream} stream readable stream */ res.stream = (stream) => { - ctx.body = stream; + let resolved = false; - finished(stream, (err) => { - isFinished = true; + /** + * @param {Error=} err error + */ + const onEvent = (err) => { + if (resolved) return; + resolved = true; + + stream.removeListener("error", onEvent); + stream.removeListener("readable", onEvent); if (err) { reject(err); - } else { - resolve(); + return; } - }); + + ctx.body = stream; + isFinished = true; + resolve(); + }; + + stream.once("error", onEvent); + stream.once("readable", onEvent); + // Empty stream + stream.once("end", onEvent); }; /** * @param {string | Buffer} data data