diff --git a/.cspell.json b/.cspell.json index 7722ca977..ecd7d2e23 100644 --- a/.cspell.json +++ b/.cspell.json @@ -3,7 +3,6 @@ "language": "en,en-gb", "words": [ "memfs", - "colorette", "noextension", "fullhash", "execa", @@ -24,7 +23,8 @@ "cachable", "finalhandler", "hono", - "rspack" + "rspack", + "malformed" ], "ignorePaths": [ "CHANGELOG.md", diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index ee0524fd6..3e183e1f0 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "Dependency Review" - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1d61d2f5e..897bbf71c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -31,12 +31,12 @@ jobs: group: lint-${{ matrix.os }}-v${{ matrix.node-version }}-${{ github.ref }} cancel-in-progress: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -53,9 +53,6 @@ jobs: - name: Check types run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi - - name: Security audit - run: npm run security - - name: Validate PR commits with commitlint if: github.event_name == 'pull_request' run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose @@ -64,9 +61,10 @@ jobs: name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [18.x, 20.x, 22.x, 24.x] + node-version: [20.x, 22.x, 24.x, 25.x] webpack-version: [latest] runs-on: ${{ matrix.os }} @@ -76,10 +74,10 @@ jobs: cancel-in-progress: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} cache: "npm" @@ -91,6 +89,6 @@ jobs: run: npm run test:coverage -- --ci - name: Submit coverage data to codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index ce8a7feda..823f108fa 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,14 @@ See [below](#other-servers) for an example of use with fastify. | **[`etag`](#tag)** | `boolean\| "weak"\| "strong"` | `undefined` | Enable or disable etag generation. | | **[`lastModified`](#lastmodified)** | `boolean` | `undefined` | Enable or disable `Last-Modified` header. Uses the file system's last modified value. | | **[`cacheControl`](#cachecontrol)** | `boolean\|number\|string\|Object` | `undefined` | Enable or disable setting `Cache-Control` response header. | -| **[`cacheImmutable`](#cacheimmutable)** | `boolean\` | `undefined` | Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets. | +| **[`cacheImmutable`](#cacheimmutable)** | `boolean` | `undefined` | Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets. | | **[`publicPath`](#publicpath)** | `string` | `undefined` | The public path that the middleware is bound to. | | **[`stats`](#stats)** | `boolean\|string\|Object` | `stats` (from a configuration) | Stats options object or preset name. | | **[`serverSideRender`](#serversiderender)** | `boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. | | **[`writeToDisk`](#writetodisk)** | `boolean\|Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. | | **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. | | **[`modifyResponseData`](#modifyresponsedata)** | `Function` | `undefined` | Allows to set up a callback to change the response data. | +| **[`forwardError`](#forwarderror)** | `boolean` | `false` | Enable or disable forwarding errors to the next middleware. | The middleware accepts an `options` Object. The following is a property reference for the Object. @@ -459,12 +460,104 @@ const app = new express(); app.use(instance); instance.waitUntilValid(() => { - const filename = instance.getFilenameFromUrl("/bundle.js"); + let resolver; + + try { + resolved = instance.getFilenameFromUrl("/bundle.js"); + } catch (err) { + console.log(`Error: ${err}`); + } + + if (!resolved) { + console.log("Not found"); + return; + } console.log(`Filename is ${filename}`); }); ``` +### `plugin(compiler, options)` + +Creates middleware instance in plugin mode. + +In plugin mode, stats output is written through custom code (i.e. in callback for `watch` or where you are calling `stats.toString(options)`) instead of `console.log`. +In this case, the `stats` option is not supported because `webpack-dev-middleware` does not have access to the code where the stats will be output. +You will also need to manually run the `watch` method. + +Why do you need this mode? In some cases, you may want to have multiple dev servers or run only one dev server when you have multiple configurations, and this is suitable for you. + +```js +const webpack = require("webpack"); +const middleware = require("webpack-dev-middleware"); + +const compiler = webpack({ + plugins: [ + { + apply(compiler) { + const devMiddleware = middleware( + compiler, + { + /* webpack-dev-middleware options */ + }, + true, + ); + }, + }, + ], + /* Webpack configuration */ +}); + +compiler.watch((err, stats) => { + if (err) { + console.error(err); + return; + } + + console.log(stats.toString()); +}); +``` + +### Plugin wrappers + +The following wrappers enable plugin mode for framework integrations: + +- `middleware(compiler, options, true)` (connect/express like middleware) +- `middleware.koaWrapper(compiler, options, true)` +- `middleware.hapiWrapper(true)` +- `middleware.honoWrapper(compiler, options, true)` + +They are equivalent to `koaWrapper`/`hapiWrapper`/`honoWrapper`, but use plugin mode logging behavior. + +### `forwardError` + +Type: `boolean` +Default: `false` + +Enable or disable forwarding errors to the next middleware. If `true`, errors will be forwarded to the next middleware, otherwise, they will be handled by `webpack-dev-middleware` and a response will be handled case by case. + +This option don't work with hono, koa and hapi, because of the differences in error handling between these frameworks and express. + +```js +const express = require("express"); +const webpack = require("webpack"); +const middleware = require("webpack-dev-middleware"); + +const compiler = webpack({ + /* Webpack configuration */ +}); + +const instance = middleware(compiler, { forwardError: true }); + +const app = express(); +app.use(instance); + +app.use((err, req, res, next) => { + console.log(`Error: ${err}`); + res.status(500).send("Something broke!"); +}); +``` + ## FAQ ### Avoid blocking requests to non-webpack resources. @@ -681,6 +774,8 @@ const devMiddlewareOptions = { const app = new Koa(); app.use(middleware.koaWrapper(compiler, devMiddlewareOptions)); +// Alternative usage (when you want to use as a plugin, i.e. all stats will be printed by other code): +// app.use(middleware.koaWrapper(compiler, devMiddlewareOptions, true)); app.listen(3000); ``` @@ -699,7 +794,7 @@ const devMiddlewareOptions = {}; const server = Hapi.server({ port: 3000, host: "localhost" }); await server.register({ - plugin: devMiddleware.hapiPlugin(), + plugin: devMiddleware.hapiWrapper(), options: { // The `compiler` option is required compiler, @@ -707,6 +802,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.hapiWrapper(true), +// options: { +// // The `compiler` option is required +// compiler, +// ...devMiddlewareOptions, +// }, +// }); + await server.start(); console.log("Server running on %s", server.info.uri); @@ -755,6 +860,9 @@ const app = new Hono(); app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions)); +// Alternative usage (when you want to use as a plugin, i.e. all stats will be printed by other code): +// const honoDevMiddleware = devMiddleware.honoWrapper(compiler, devMiddlewareOptions, true) + serve(app); ``` diff --git a/package-lock.json b/package-lock.json index 5b2ed65cb..1cf0b248c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,11 @@ "version": "7.4.5", "license": "MIT", "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.43.1", - "mime-types": "^3.0.1", + "memfs": "^4.56.10", + "mime-types": "^3.0.2", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "schema-utils": "^4.3.3" }, "devDependencies": { "@babel/cli": "^7.16.7", @@ -57,14 +56,14 @@ "webpack": "^5.101.0" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.101.0" }, "peerDependenciesMeta": { "webpack": { @@ -133,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", @@ -2274,6 +2274,7 @@ "integrity": "sha512-Tdfx4eH2uS+gv9V9NCr3Rz+c7RSS6ntXp3Blliud18ibRUlRxO9dTaOjG4iv4x0nAmMeedP1ORkEpeXSkh2QiQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -2355,7 +2356,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", @@ -2495,14 +2497,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", @@ -2700,7 +2704,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", @@ -5543,6 +5548,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5675,6 +5681,7 @@ "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5768,6 +5775,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", @@ -5807,6 +5815,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", @@ -6509,6 +6518,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6551,6 +6561,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", @@ -7234,6 +7245,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7753,6 +7765,7 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -10207,6 +10220,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", @@ -10296,6 +10310,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -11759,6 +11774,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", @@ -13138,6 +13154,7 @@ "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -14159,6 +14176,7 @@ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", @@ -18422,6 +18440,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -20805,6 +20824,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -20984,7 +21004,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", @@ -21138,6 +21159,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21507,6 +21529,7 @@ "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -22074,6 +22097,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 4ec83b262..df1b8d900 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,11 @@ "release": "standard-version" }, "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.43.1", - "mime-types": "^3.0.1", + "memfs": "^4.56.10", + "mime-types": "^3.0.2", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "schema-utils": "^4.3.3" }, "devDependencies": { "@babel/cli": "^7.16.7", @@ -95,7 +94,7 @@ "webpack": "^5.101.0" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.101.0" }, "peerDependenciesMeta": { "webpack": { @@ -103,6 +102,6 @@ } }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" } } diff --git a/src/index.js b/src/index.js index 8cc01b439..49b1fd222 100644 --- a/src/index.js +++ b/src/index.js @@ -125,15 +125,16 @@ const noop = () => {}; * @property {boolean=} lastModified options to generate last modified header * @property {(boolean | number | string | { maxAge?: number, immutable?: boolean })=} cacheControl options to generate cache headers * @property {boolean=} cacheImmutable is cache immutable + * @property {boolean=} forwardError forward error to next middleware */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @callback Middleware - * @param {RequestInternal} req - * @param {ResponseInternal} res - * @param {NextFunction} next + * @param {RequestInternal} req request + * @param {ResponseInternal} res response + * @param {NextFunction} next next function * @returns {Promise} */ @@ -141,9 +142,8 @@ const noop = () => {}; /** * @callback GetFilenameFromUrl - * @param {string} url - * @param {Extra=} extra - * @returns {string | undefined} + * @param {string} url request URL + * @returns {{ filename: string, extra: Extra } | undefined} a filename with additional information, or `undefined` if nothing is found */ /** @@ -195,9 +195,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", @@ -219,7 +220,6 @@ function wdm(compiler, options = {}) { */ const context = { state: false, - stats: undefined, callbacks: [], options, @@ -227,7 +227,7 @@ function wdm(compiler, options = {}) { logger: compiler.getInfrastructureLogger("webpack-dev-middleware"), }; - setupHooks(context); + setupHooks(context, isPlugin); if (typeof options.writeToDisk === "function") { setupWriteToDisk(context); @@ -236,36 +236,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); + } } } @@ -278,8 +280,7 @@ function wdm(compiler, options = {}) { (middleware(filledContext)); // API - instance.getFilenameFromUrl = (url, extra) => - getFilenameFromUrl(filledContext, url, extra); + instance.getFilenameFromUrl = (url) => getFilenameFromUrl(filledContext, url); instance.waitUntilValid = (callback = noop) => { ready(filledContext, callback); @@ -320,9 +321,10 @@ function wdm(compiler, options = {}) { /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal + * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false * @returns {HapiPlugin} hapi wrapper */ -function hapiWrapper() { +function hapiWrapper(usePlugin = false) { return { pkg: { name: "webpack-dev-middleware", @@ -336,7 +338,7 @@ function hapiWrapper() { throw new Error("The compiler options is required."); } - const devMiddleware = wdm(compiler, rest); + const devMiddleware = wdm(compiler, rest, usePlugin); // @ts-expect-error if (!server.decorations.server.includes("webpackDevMiddleware")) { @@ -395,10 +397,11 @@ wdm.hapiWrapper = hapiWrapper; * @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) { - const devMiddleware = wdm(compiler, options); +function koaWrapper(compiler, options, usePlugin) { + const devMiddleware = wdm(compiler, options, usePlugin); /** * @param {{ req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | { message: string }, state: object }} ctx context @@ -440,10 +443,32 @@ function koaWrapper(compiler, options) { * @param {import("fs").ReadStream} stream readable stream */ res.stream = (stream) => { - ctx.body = stream; + let resolved = false; - isFinished = true; - resolve(); + /** + * @param {Error=} err error + */ + const onEvent = (err) => { + if (resolved) return; + resolved = true; + + stream.removeListener("error", onEvent); + stream.removeListener("readable", onEvent); + + if (err) { + reject(err); + 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 @@ -481,6 +506,13 @@ function koaWrapper(compiler, options) { }, ); } catch (err) { + if (options?.forwardError) { + await next(); + + // need the return for prevent to execute the code below and override the status and body set by user in the next middleware + return; + } + ctx.status = /** @type {Error & { statusCode: number }} */ (err).statusCode || /** @type {Error & { status: number }} */ (err).status || @@ -508,10 +540,11 @@ wdm.koaWrapper = koaWrapper; * @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 */ -function honoWrapper(compiler, options) { - const devMiddleware = wdm(compiler, options); +function honoWrapper(compiler, options, usePlugin) { + 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 @@ -606,10 +639,38 @@ function honoWrapper(compiler, options) { * @param {import("fs").ReadStream} stream readable stream */ res.stream = (stream) => { - body = stream; + let isResolved = false; + + /** + * @param {Error=} err err + */ + const onEvent = (err) => { + if (isResolved) return; + isResolved = true; + + stream.removeListener("error", onEvent); + stream.removeListener("readable", onEvent); + stream.removeListener("end", onEvent); + + if (err) { + stream.destroy(); + reject(err); + return; + } + + body = stream; + isFinished = true; + resolve(); + }; - isFinished = true; - resolve(); + stream.once("error", onEvent); + stream.once("readable", onEvent); + // Empty stream + stream.once("end", onEvent); + + if (stream.pending === false) { + onEvent(); + } }; /** @@ -655,6 +716,12 @@ function honoWrapper(compiler, options) { }, ); } catch (err) { + if (options?.forwardError) { + await next(); + + // need the return for prevent to execute the code below and override the status and body set by user in the next middleware + return; + } context.status(500); return context.json({ message: /** @type {Error} */ (err).message }); diff --git a/src/middleware.js b/src/middleware.js index 7b38ea99b..063f672ab 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -31,6 +31,8 @@ const ready = require("./utils/ready"); /** @typedef {import("./index.js").IncomingMessage} IncomingMessage */ /** @typedef {import("./index.js").ServerResponse} ServerResponse */ /** @typedef {import("./index.js").NormalizedHeaders} NormalizedHeaders */ +/** @typedef {import("./utils/getFilenameFromUrl.js").FilenameError} FilenameError */ +/** @typedef {import("./utils/getFilenameFromUrl.js").Extra} Extra */ /** @typedef {import("fs").ReadStream} ReadStream */ const BYTES_RANGE_REGEXP = /^ *bytes/i; @@ -159,8 +161,6 @@ function wrapper(context) { } const acceptedMethods = context.options.methods || ["GET", "HEAD"]; - // TODO do we need an option here? - const forwardError = false; initState(res); @@ -178,13 +178,24 @@ function wrapper(context) { * @returns {Promise} */ async function sendError(message, status, options) { - if (forwardError) { + if (context.options.forwardError) { + if (!getHeadersSent(res)) { + const headers = getResponseHeaders(res); + + for (let i = 0; i < headers.length; i++) { + removeResponseHeader(res, headers[i]); + } + } + const error = /** @type {Error & { statusCode: number }} */ (new Error(message)); error.statusCode = status; await goNext(error); + + // need the return for prevent to execute the code below and override the status and body set by user in the next middleware + return; } const escapeHtml = getEscapeHtml(); @@ -498,22 +509,29 @@ function wrapper(context) { */ async function processRequest() { // Pipe and SendFile - /** @type {import("./utils/getFilenameFromUrl").Extra} */ - const extra = {}; - const filename = getFilenameFromUrl( - context, - /** @type {string} */ (getRequestURL(req)), - extra, - ); - - if (extra.errorCode) { - if (extra.errorCode === 403) { - context.logger.error(`Malicious path "${filename}".`); + /** @type {{ filename: string, extra: Extra } | undefined} */ + let resolved; + + const requestUrl = /** @type {string} */ (getRequestURL(req)); + + try { + resolved = getFilenameFromUrl(context, requestUrl); + } catch (err) { + // Fallback to 403 for unknown errors + const errorCode = + typeof err === "object" && + err !== null && + typeof (/** @type {FilenameError} */ (err).code) !== "undefined" + ? /** @type {FilenameError} */ (err).code + : 403; + + if (errorCode === 403) { + context.logger.error(`Malicious path "${requestUrl}".`); } await sendError( - extra.errorCode === 400 ? "Bad Request" : "Forbidden", - extra.errorCode, + errorCode === 400 ? "Bad Request" : "Forbidden", + errorCode, { modifyResponseData: context.options.modifyResponseData, }, @@ -521,7 +539,7 @@ function wrapper(context) { return; } - if (!filename) { + if (!resolved) { await goNext(); return; } @@ -531,7 +549,8 @@ function wrapper(context) { return; } - const { size } = /** @type {import("fs").Stats} */ (extra.stats); + const { extra, filename } = resolved; + const { size } = extra.stats; let len = size; let offset = 0; @@ -571,40 +590,36 @@ function wrapper(context) { } if (!getResponseHeader(res, "Cache-Control")) { - // TODO enable the `cacheImmutable` by default for the next major release - const cacheControl = - context.options.cacheImmutable && extra.immutable - ? { immutable: true } - : context.options.cacheControl; - - if (cacheControl) { - let cacheControlValue; - - if (typeof cacheControl === "boolean") { - cacheControlValue = "public, max-age=31536000"; - } else if (typeof cacheControl === "number") { - const maxAge = Math.floor( - Math.min(Math.max(0, cacheControl), MAX_MAX_AGE) / 1000, - ); + const { cacheControl, cacheImmutable } = context.options; - cacheControlValue = `public, max-age=${maxAge}`; - } else if (typeof cacheControl === "string") { - cacheControlValue = cacheControl; - } else { - const maxAge = cacheControl.maxAge - ? Math.floor( - Math.min(Math.max(0, cacheControl.maxAge), MAX_MAX_AGE) / - 1000, - ) - : MAX_MAX_AGE / 1000; - - cacheControlValue = `public, max-age=${maxAge}`; - - if (cacheControl.immutable) { - cacheControlValue += ", immutable"; - } + let cacheControlValue; + + if ( + (cacheImmutable === undefined || cacheImmutable) && + extra.immutable + ) { + cacheControlValue = `public, max-age=${Math.floor(MAX_MAX_AGE / 1000)}, immutable`; + } else if (typeof cacheControl === "boolean") { + cacheControlValue = `public, max-age=${Math.floor(MAX_MAX_AGE / 1000)}`; + } else if (typeof cacheControl === "number") { + const maxAge = Math.min(Math.max(0, cacheControl), MAX_MAX_AGE); + cacheControlValue = `public, max-age=${Math.floor(maxAge / 1000)}`; + } else if (typeof cacheControl === "string") { + cacheControlValue = cacheControl; + } else if (cacheControl) { + const maxAge = + cacheControl.maxAge !== undefined + ? Math.min(Math.max(0, cacheControl.maxAge), MAX_MAX_AGE) + : MAX_MAX_AGE; + + cacheControlValue = `public, max-age=${Math.floor(maxAge / 1000)}`; + + if (cacheControl.immutable) { + cacheControlValue += ", immutable"; } + } + if (cacheControlValue) { setResponseHeader(res, "Cache-Control", cacheControlValue); } } @@ -613,9 +628,7 @@ function wrapper(context) { context.options.lastModified && !getResponseHeader(res, "Last-Modified") ) { - const modified = - /** @type {import("fs").Stats} */ - (extra.stats).mtime.toUTCString(); + const modified = extra.stats.mtime.toUTCString(); setResponseHeader(res, "Last-Modified", modified); } @@ -671,7 +684,7 @@ function wrapper(context) { const result = await getETag()( isStrongETag ? /** @type {Buffer | ReadStream} */ (bufferOrStream) - : /** @type {import("fs").Stats} */ (extra.stats), + : extra.stats, ); // Because we already read stream, we can cache buffer to avoid extra read from fs @@ -863,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/src/options.json b/src/options.json index 0a55b69c9..1e83adaa1 100644 --- a/src/options.json +++ b/src/options.json @@ -172,6 +172,11 @@ "description": "Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).", "link": "https://github.com/webpack/webpack-dev-middleware#cacheimmutable", "type": "boolean" + }, + "forwardError": { + "description": "Enable or disable forwarding errors to next middleware.", + "link": "https://github.com/webpack/webpack-dev-middleware#forwarderrors", + "type": "boolean" } }, "additionalProperties": false diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index 44c7b7bd6..d54bd749a 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -1,7 +1,5 @@ const path = require("node:path"); const querystring = require("node:querystring"); -// eslint-disable-next-line n/no-deprecated-api -const { parse } = require("node:url"); const getPaths = require("./getPaths"); const memorize = require("./memorize"); @@ -17,20 +15,18 @@ function decode(input) { return querystring.unescape(input); } -const memoizedParse = memorize(parse, undefined, (value) => { - if (value.pathname) { - value.pathname = decode(value.pathname); - } +const memoizedParse = memorize((url) => { + const urlObject = new URL(url, "http://localhost"); - return value; -}); + // We can't change pathname in URL object directly because don't decode correctly + return { ...urlObject, pathname: decode(urlObject.pathname) }; +}, undefined); const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/; /** * @typedef {object} Extra - * @property {import("fs").Stats=} stats stats - * @property {number=} errorCode error code + * @property {import("fs").Stats} stats stats * @property {boolean=} immutable true when immutable, otherwise false */ @@ -42,43 +38,55 @@ const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/; * @returns {string} */ -// TODO refactor me in the next major release, this function should return `{ filename, stats, error }` +class FilenameError extends Error { + /** + * @param {string} message message + * @param {number=} code error code + */ + constructor(message, code) { + super(message); + this.name = "FilenameError"; + this.code = code; + } +} + // TODO fix redirect logic when `/` at the end, like https://github.com/pillarjs/send/blob/master/index.js#L586 /** * @template {IncomingMessage} Request * @template {ServerResponse} Response * @param {import("../index.js").FilledContext} context context * @param {string} url url - * @param {Extra=} extra extra - * @returns {string | undefined} filename + * @returns {{ filename: string, extra: Extra } | undefined} result of get filename from url */ -function getFilenameFromUrl(context, url, extra = {}) { - const { options } = context; - const paths = getPaths(context); +function getFilenameFromUrl(context, url) { + /** @type {URL} */ + let urlObject; /** @type {string | undefined} */ let foundFilename; - /** @type {import("node:url").Url} */ - let urlObject; try { // The `url` property of the `request` is contains only `pathname`, `search` and `hash` - urlObject = memoizedParse(url, false, true); + urlObject = memoizedParse(url); } catch { return; } + const { options } = context; + const paths = getPaths(context); + + /** @type {Extra} */ + const extra = {}; + for (const { publicPath, outputPath, assetsInfo } of paths) { /** @type {string | undefined} */ let filename; - /** @type {import("node:url").Url} */ + /** @type {URL} */ let publicPathObject; try { publicPathObject = memoizedParse( publicPath !== "auto" && publicPath ? publicPath : "/", - false, - true, ); } catch { continue; @@ -94,16 +102,12 @@ function getFilenameFromUrl(context, url, extra = {}) { ) { // Null byte(s) if (pathname.includes("\0")) { - extra.errorCode = 400; - - return; + throw new FilenameError("Bad Request", 400); } // ".." is malicious if (UP_PATH_REGEXP.test(path.normalize(`./${pathname}`))) { - extra.errorCode = 403; - - return; + throw new FilenameError("Forbidden", 403); } // Strip the `pathname` property from the `publicPath` option from the start of requested url @@ -161,7 +165,12 @@ function getFilenameFromUrl(context, url, extra = {}) { } } - return foundFilename; + if (!foundFilename) { + return; + } + + return { filename: foundFilename, extra }; } module.exports = getFilenameFromUrl; +module.exports.FilenameError = FilenameError; diff --git a/src/utils/setupHooks.js b/src/utils/setupHooks.js index f9b14c8de..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} */ @@ -54,7 +55,6 @@ 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 @@ -66,92 +66,85 @@ function setupHooks(context) { 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; - } + // For plugin support we should print nothing, because webpack/webpack-cli/webpack-dev-server will print them on using `stats.toString()` + if (!isPlugin) { + logger.log("Compilation finished"); - 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] = + 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; - - // TODO remove `colorette` and set minimum supported webpack version is `5.101.0` - childStatsOptions.colors = - typeof firstCompiler.webpack !== "undefined" && - typeof firstCompiler.webpack.cli !== "undefined" && - typeof firstCompiler.webpack.cli.isColorSupported === - "function" - ? firstCompiler.webpack.cli.isColorSupported() - : require("colorette").isColorSupported; + (compiler).compilers.map((child) => child.options.stats), } + : /** @type {Compiler} */ (compiler).options.stats; + } - return childStatsOptions; - }, + 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), ); - } else { - statsOptions = normalizeStatsOptions( - /** @type {StatsOptions} */ (statsOptions), - ); - if (typeof statsOptions.colors === "undefined") { - const { compiler } = /** @type {{ compiler: Compiler }} */ (context); - // TODO remove `colorette` and set minimum supported webpack version is `5.101.0` - statsOptions.colors = - typeof compiler.webpack !== "undefined" && - typeof compiler.webpack.cli !== "undefined" && - typeof compiler.webpack.cli.isColorSupported === "function" - ? compiler.webpack.cli.isColorSupported() - : require("colorette").isColorSupported; + if (typeof statsOptions.colors === "undefined") { + const { compiler } = /** @type {{ compiler: Compiler }} */ ( + context + ); + statsOptions.colors = compiler.webpack.cli.isColorSupported(); + } } - } - const printedStats = stats.toString( - /** @type {StatsObjectOptions} */ - (statsOptions), - ); + 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); + // Avoid extra empty line when `stats: 'none'` + if (printedStats) { + // eslint-disable-next-line no-console + console.log(printedStats); + } } context.callbacks = []; diff --git a/test/__snapshots__/logging.test.js.snap.webpack5 b/test/__snapshots__/logging.test.js.snap.webpack5 index e5a367df7..1bc9bec7a 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,619 @@ 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) +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`] = `""`; + +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) +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`] = ` +"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`] = ` +"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`] = `""`; + +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) +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 "true" value: stderr 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) +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 "verbose" value: stderr 1`] = `""`; + +exports[`logging plugin should logging on successfully build using the "stats" option for middleware with the "verbose" 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 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: 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 when the 'stats' doesn't exist: stderr 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) +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: stderr 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) +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 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: 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 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: 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 "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`] = ` +"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`] = `""`; + +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) +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 "true" value: stderr 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) +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 object value: stderr 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) +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 unsuccessful build in multi-compiler: stderr 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 +> 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 plugin should logging on unsuccessful build: stderr 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 +> 1()2()3() +| + +webpack compiled with 1 error" +`; + +exports[`logging plugin should logging warnings in multi-compiler mode: stderr 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 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: stderr 1`] = `""`; -exports[`logging should logging on successfully build using the "stats" option for middleware with object value: stdout 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 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: stderr 1`] = `""`; -exports[`logging 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 "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 standalone 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 standalone 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 standalone 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 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) @@ -332,9 +918,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 standalone 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 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) @@ -346,9 +932,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 standalone 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 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) @@ -389,17 +975,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 standalone 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 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 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: stderr 1`] = `""`; -exports[`logging should logging on successfully build when the 'stats' doesn't exist: stdout 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) @@ -411,9 +997,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 standalone should logging on successfully build: stderr 1`] = `""`; -exports[`logging should logging on successfully build: stdout 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) @@ -425,9 +1011,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 standalone 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 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) @@ -435,9 +1021,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 standalone 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 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) @@ -445,13 +1031,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 standalone 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 standalone 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 standalone 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 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) @@ -467,9 +1053,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 standalone 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 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) @@ -485,9 +1071,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 standalone 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 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) @@ -495,9 +1081,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 standalone should logging on unsuccessful build in multi-compiler: stderr 1`] = `""`; -exports[`logging should logging on unsuccessful build in multi-compiler: stdout 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 @@ -515,9 +1101,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 standalone should logging on unsuccessful build: stderr 1`] = `""`; -exports[`logging should logging on unsuccessful build: stdout 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 @@ -527,9 +1113,9 @@ 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 standalone should logging warnings in multi-compiler mode: stderr 1`] = `""`; -exports[`logging should logging warnings in multi-compiler mode: stdout 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/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.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/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/helpers/runner.js b/test/helpers/runner.js index 13fba0cae..a21554efb 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,148 @@ 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; + } + } +}; + +/** + * @param {import("webpack").Compiler} compiler compiler + * @param {import("webpack").StatsOptions} statsOptions stats options + * @returns {{ preset: string }} normalized stats + */ +function normalizeStatsOptions(compiler, 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 }; + } + + if (typeof statsOptions.colors === "undefined") { + statsOptions.colors = compiler.webpack.cli.isColorSupported(); + } -app.use(instance); -app.listen((error) => { - if (error) { - throw error; + // 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; } - 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; +} + +/** + * @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({ + apply(compiler) { + let app; + + compiler.hooks.done.tap("webpack-dev-middleware-test", () => { + const instance = middleware(compiler, configMiddleware, true); + + app = express(); + app.use(instance); + app.listen((error) => { + if (error) { + throw error; + } + + process.stdin.on("data", handleStdin); + }); + }); + }, + }); + + 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; + } + + if (process.env.WEBPACK_BREAK_WATCH) { + const error = new Error("Watch error"); + error.code = "watch error"; + + 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)); }); -}); +} 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 aa9109818..117ef1d32 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; - import { stripVTControlCharacters } from "node:util"; + import execa from "execa"; function extractErrorEntry(string) { @@ -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", @@ -105,7 +116,7 @@ describe("logging", () => { }); 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", @@ -143,7 +154,7 @@ describe("logging", () => { }); 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", @@ -181,7 +192,7 @@ describe("logging", () => { }); 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", @@ -219,7 +230,7 @@ describe("logging", () => { }); 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", @@ -255,7 +266,7 @@ describe("logging", () => { }); 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", @@ -293,7 +304,7 @@ describe("logging", () => { }); 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", @@ -331,7 +342,7 @@ describe("logging", () => { }); 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", @@ -369,7 +380,7 @@ describe("logging", () => { }); 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", @@ -405,7 +416,7 @@ describe("logging", () => { }); 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", @@ -443,10 +454,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", FORCE_COLOR: true, }, }); @@ -481,7 +492,7 @@ describe("logging", () => { }); it("should logging on unsuccessful build", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.error.config", @@ -519,7 +530,7 @@ describe("logging", () => { }); 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", @@ -557,7 +568,7 @@ describe("logging", () => { }); it("should logging an warning", (done) => { - const proc = execa(runner, [], { + const proc = execa(runner, args, { stdio: "pipe", env: { WEBPACK_CONFIG: "webpack.warning.config", @@ -595,7 +606,7 @@ describe("logging", () => { }); 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", @@ -633,7 +644,7 @@ describe("logging", () => { }); 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", @@ -671,7 +682,7 @@ describe("logging", () => { }); 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: @@ -710,7 +721,7 @@ describe("logging", () => { }); 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", @@ -748,7 +759,7 @@ describe("logging", () => { }); 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", @@ -786,10 +797,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.dev-server-false-logging", FORCE_COLOR: true, }, }); @@ -824,7 +835,7 @@ describe("logging", () => { }); 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, @@ -859,7 +870,7 @@ describe("logging", () => { 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", @@ -893,7 +904,7 @@ describe("logging", () => { } 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", @@ -932,7 +943,7 @@ describe("logging", () => { }); 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", @@ -970,7 +981,7 @@ describe("logging", () => { }); 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", @@ -1008,7 +1019,7 @@ describe("logging", () => { }); 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", @@ -1047,7 +1058,7 @@ describe("logging", () => { }); 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", @@ -1086,7 +1097,7 @@ describe("logging", () => { }); 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", @@ -1125,7 +1136,7 @@ describe("logging", () => { }); 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", @@ -1164,7 +1175,7 @@ describe("logging", () => { }); 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", @@ -1203,10 +1214,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WMC_stats: true, FORCE_COLOR: true, }, @@ -1242,10 +1253,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WMC_stats: false, FORCE_COLOR: true, }, @@ -1280,10 +1291,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WMC_stats: "normal", FORCE_COLOR: true, }, @@ -1319,10 +1330,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WEBPACK_DEV_MIDDLEWARE_STATS: "object", FORCE_COLOR: true, }, @@ -1358,10 +1369,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WEBPACK_DEV_MIDDLEWARE_STATS: "object_colors_true", FORCE_COLOR: true, }, @@ -1397,10 +1408,10 @@ describe("logging", () => { }); 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", + WEBPACK_CONFIG: "webpack.array.logging.config.js", WEBPACK_DEV_MIDDLEWARE_STATS: "object_colors_false", FORCE_COLOR: true, }, @@ -1436,7 +1447,7 @@ describe("logging", () => { }); 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/test/middleware.test.js b/test/middleware.test.js index 8d676b43d..02fda1cf6 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -146,7 +146,6 @@ async function frameworkFactory( app.use(item); } } - return [server, req, instance.devMiddleware]; } default: { @@ -573,16 +572,16 @@ describe.each([ it("should work", (done) => { instance.waitUntilValid(() => { - expect(instance.getFilenameFromUrl("/bundle.js")).toBe( + expect(instance.getFilenameFromUrl("/bundle.js").filename).toBe( path.join(webpackConfig.output.path, "/bundle.js"), ); - expect(instance.getFilenameFromUrl("/")).toBe( + expect(instance.getFilenameFromUrl("/").filename).toBe( path.join(webpackConfig.output.path, "/index.html"), ); - expect(instance.getFilenameFromUrl("/index.html")).toBe( + expect(instance.getFilenameFromUrl("/index.html").filename).toBe( path.join(webpackConfig.output.path, "/index.html"), ); - expect(instance.getFilenameFromUrl("/svg.svg")).toBe( + expect(instance.getFilenameFromUrl("/svg.svg").filename).toBe( path.join(webpackConfig.output.path, "/svg.svg"), ); expect( @@ -617,15 +616,15 @@ describe.each([ it("should work", (done) => { instance.waitUntilValid(() => { - expect(instance.getFilenameFromUrl("/bundle.js")).toBe( + expect(instance.getFilenameFromUrl("/bundle.js").filename).toBe( path.join(webpackConfig.output.path, "/bundle.js"), ); expect(instance.getFilenameFromUrl("/")).toBeUndefined(); - expect(instance.getFilenameFromUrl("/index.html")).toBe( + expect(instance.getFilenameFromUrl("/index.html").filename).toBe( path.join(webpackConfig.output.path, "/index.html"), ); - expect(instance.getFilenameFromUrl("/svg.svg")).toBe( + expect(instance.getFilenameFromUrl("/svg.svg").filename).toBe( path.join(webpackConfig.output.path, "/svg.svg"), ); expect( @@ -658,19 +657,23 @@ describe.each([ it("should work", (done) => { instance.waitUntilValid(() => { expect( - instance.getFilenameFromUrl("/public/path/bundle.js"), + instance.getFilenameFromUrl("/public/path/bundle.js").filename, ).toBe( path.join(webpackPublicPathConfig.output.path, "/bundle.js"), ); - expect(instance.getFilenameFromUrl("/public/path/")).toBe( + expect( + instance.getFilenameFromUrl("/public/path/").filename, + ).toBe( path.join(webpackPublicPathConfig.output.path, "/index.html"), ); expect( - instance.getFilenameFromUrl("/public/path/index.html"), + instance.getFilenameFromUrl("/public/path/index.html").filename, ).toBe( path.join(webpackPublicPathConfig.output.path, "/index.html"), ); - expect(instance.getFilenameFromUrl("/public/path/svg.svg")).toBe( + expect( + instance.getFilenameFromUrl("/public/path/svg.svg").filename, + ).toBe( path.join(webpackPublicPathConfig.output.path, "/svg.svg"), ); @@ -704,20 +707,22 @@ describe.each([ it("should work", (done) => { instance.waitUntilValid(() => { - expect(instance.getFilenameFromUrl("/static-one/bundle.js")).toBe( + expect( + instance.getFilenameFromUrl("/static-one/bundle.js").filename, + ).toBe( path.join(webpackMultiConfig[0].output.path, "/bundle.js"), ); - expect(instance.getFilenameFromUrl("/static-one/")).toBe( + expect(instance.getFilenameFromUrl("/static-one/").filename).toBe( path.join(webpackMultiConfig[0].output.path, "/index.html"), ); expect( - instance.getFilenameFromUrl("/static-one/index.html"), + instance.getFilenameFromUrl("/static-one/index.html").filename, ).toBe( path.join(webpackMultiConfig[0].output.path, "/index.html"), ); - expect(instance.getFilenameFromUrl("/static-one/svg.svg")).toBe( - path.join(webpackMultiConfig[0].output.path, "/svg.svg"), - ); + expect( + instance.getFilenameFromUrl("/static-one/svg.svg").filename, + ).toBe(path.join(webpackMultiConfig[0].output.path, "/svg.svg")); expect( instance.getFilenameFromUrl("/static-one/unknown.unknown"), ).toBeUndefined(); @@ -727,7 +732,9 @@ describe.each([ ), ).toBeUndefined(); - expect(instance.getFilenameFromUrl("/static-two/bundle.js")).toBe( + expect( + instance.getFilenameFromUrl("/static-two/bundle.js").filename, + ).toBe( path.join(webpackMultiConfig[1].output.path, "/bundle.js"), ); expect( @@ -1244,13 +1251,8 @@ describe.each([ expect(response.statusCode).toBe(200); - // todo bug on node.js@22 and memfs - const [major] = process.versions.node.split(".").map(Number); - - if (major < 22) { - expect(response.text).toBe("\u00BD + \u00BC = \u00BE"); - expect(response.headers["content-length"]).toBe("12"); - } + expect(response.text).toBe("\u00BD + \u00BC = \u00BE"); + expect(response.headers["content-length"]).toBe("12"); expect(response.headers["content-type"]).toBe( "text/html; charset=utf-8", @@ -1294,7 +1296,7 @@ describe.each([ }); }); - describe('should not work with the broken "publicPath" option', () => { + describe('should work with the broken "publicPath" option (malformed URI parsed as "/")', () => { let compiler; const outputPath = path.resolve(__dirname, "./outputs/basic"); @@ -1305,7 +1307,7 @@ describe.each([ output: { filename: "bundle.js", path: outputPath, - publicPath: "https://test:malfor%5Med@test.example.com", + publicPath: "http/s://test:malformed%5Med@test.example.com", }, }); @@ -1448,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 () => { @@ -1469,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 () => { @@ -1508,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 () => { @@ -3607,6 +3611,230 @@ describe.each([ }); }); + describe("should call the next middleware for finished or errored requests when forwardError is enabled", () => { + let compiler; + + const outputPath = path.resolve( + __dirname, + "./outputs/basic-test-errors-headers-sent", + ); + + let nextWasCalled = false; + + beforeAll(async () => { + compiler = getCompiler({ + ...webpackConfig, + output: { + filename: "bundle.js", + path: outputPath, + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { + etag: "weak", + lastModified: true, + forwardError: true, + }, + { + setupMiddlewares: (middlewares) => { + if (name === "hapi") { + // There's no such thing as "the next route handler" in hapi. One request is matched to one or no route handlers. + } else if (name === "koa") { + middlewares.push(async (ctx) => { + nextWasCalled = true; + ctx.status = 500; + ctx.body = "error"; + }); + } else if (name === "hono") { + middlewares.push(async (ctx) => { + nextWasCalled = true; + ctx.status(500); + return ctx.text("error"); + }); + } else { + middlewares.push((_error, _req, res, _next) => { + nextWasCalled = true; + res.statusCode = 500; + res.end("error"); + }); + } + + return middlewares; + }, + }, + ); + + instance.context.outputFileSystem.mkdirSync(outputPath, { + recursive: true, + }); + instance.context.outputFileSystem.writeFileSync( + path.resolve(outputPath, "index.html"), + "HTML", + ); + instance.context.outputFileSystem.writeFileSync( + path.resolve(outputPath, "image.svg"), + "svg image", + ); + instance.context.outputFileSystem.writeFileSync( + path.resolve(outputPath, "file.text"), + "text", + ); + + const originalMethod = + instance.context.outputFileSystem.createReadStream; + + instance.context.outputFileSystem.createReadStream = + function createReadStream(...args) { + if (args[0].endsWith("image.svg")) { + const brokenStream = new this.ReadStream(...args); + + brokenStream._read = function _read() { + const error = new Error("test"); + error.code = "ENAMETOOLONG"; + this.emit("error", error); + this.end(); + this.destroy(); + }; + + return brokenStream; + } + + return originalMethod(...args); + }; + }); + + beforeEach(() => { + nextWasCalled = false; + }); + + afterAll(async () => { + await close(server, instance); + }); + + it("should work with piping stream", async () => { + const response1 = await req.get("/file.text"); + + expect(response1.statusCode).toBe(200); + expect(nextWasCalled).toBe(false); + }); + + it('should return the "500" code for requests above root', async () => { + const response = await req.get("/public/..%2f../middleware.test.js"); + + expect(response.statusCode).toBe(500); + if (name !== "hapi") { + expect(response.text).toBe("error"); + expect(nextWasCalled).toBe(true); + } else { + expect(nextWasCalled).toBe(false); + } + }); + + it('should return the "500" code for the "GET" request to the bundle file with etag and wrong "if-match" header', async () => { + const response1 = await req.get("/file.text"); + + expect(response1.statusCode).toBe(200); + expect(response1.headers.etag).toBeDefined(); + expect(response1.headers.etag.startsWith("W/")).toBe(true); + + const response2 = await req.get("/file.text").set("if-match", "test"); + + expect(response2.statusCode).toBe(500); + if (name !== "hapi") { + expect(response2.text).toBe("error"); + expect(nextWasCalled).toBe(true); + } else { + expect(nextWasCalled).toBe(false); + } + }); + + it('should return the "500" code for the "GET" request with the invalid range header', async () => { + const response = await req + .get("/file.text") + .set("Range", "bytes=9999999-"); + + expect(response.statusCode).toBe(500); + if (name !== "hapi") { + expect(response.text).toBe("error"); + expect(nextWasCalled).toBe(true); + } else { + 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); + + // hapi doesn't support passthrough errors? + if (name === "hapi") { + expect(nextWasCalled).toBe(false); + } else { + expect(nextWasCalled).toBe(true); + } + }); + + it('should return the "200" code for the "HEAD" request to the bundle file', async () => { + const response = await req.head("/file.text"); + + expect(response.statusCode).toBe(200); + expect(response.text).toBeUndefined(); + expect(nextWasCalled).toBe(false); + }); + + it('should return the "304" code for the "GET" request to the bundle file with etag and "if-none-match"', async () => { + const response1 = await req.get("/file.text"); + + expect(response1.statusCode).toBe(200); + expect(response1.headers.etag).toBeDefined(); + expect(response1.headers.etag.startsWith("W/")).toBe(true); + + const response2 = await req + .get("/file.text") + .set("if-none-match", response1.headers.etag); + + expect(response2.statusCode).toBe(304); + expect(response2.headers.etag).toBeDefined(); + expect(response2.headers.etag.startsWith("W/")).toBe(true); + + const response3 = await req + .get("/file.text") + .set("if-none-match", response1.headers.etag); + + expect(response3.statusCode).toBe(304); + expect(response3.headers.etag).toBeDefined(); + expect(response3.headers.etag.startsWith("W/")).toBe(true); + expect(nextWasCalled).toBe(false); + }); + + it('should return the "304" code for the "GET" request to the bundle file with lastModified and "if-modified-since" header', async () => { + const response1 = await req.get("/file.text"); + + expect(response1.statusCode).toBe(200); + expect(response1.headers["last-modified"]).toBeDefined(); + + const response2 = await req + .get("/file.text") + .set("if-modified-since", response1.headers["last-modified"]); + + expect(response2.statusCode).toBe(304); + expect(response2.headers["last-modified"]).toBeDefined(); + + const response3 = await req + .get("/file.text") + .set("if-modified-since", response2.headers["last-modified"]); + + expect(response3.statusCode).toBe(304); + expect(response3.headers["last-modified"]).toBeDefined(); + expect(nextWasCalled).toBe(false); + }); + }); + describe("should fallthrough for not found files", () => { let compiler; @@ -4005,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(0); - expect(fs.existsSync(bundlePath)).toBe(true); + expect( + compiler.hooks.assetEmitted.taps.filter( + (hook) => hook.name === "DevMiddleware", + ), + ).toHaveLength(0); + 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(0); + return compiler.hooks.done.tap( + "DevMiddlewareWriteToDiskTest", + () => { + expect( + compiler.hooks.assetEmitted.taps.filter( + (hook) => hook.name === "DevMiddleware", + ), + ).toHaveLength(0); - 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(` @@ -4095,9 +4319,8 @@ describe.each([
Forbidden
`); - }); - }, - ); + }); + }); describe('should work with "true" value when the `output.clean` is `true`', () => { const outputPath = path.resolve( @@ -5964,7 +6187,7 @@ describe.each([ await close(server, instance); }); - it('should return the "200" code for the "GET" request to the bundle file and don\'t generate `Cache-Control` header', async () => { + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header', async () => { const response = await req.get("/bundle.js"); expect(response.statusCode).toBe(200); @@ -6129,7 +6352,6 @@ describe.each([ name, framework, compiler, - { cacheImmutable: true }, ); }); @@ -6235,6 +6457,332 @@ describe.each([ ); }); }); + + describe("should not generate `Cache-Control` header for immutable assets when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and don\'t generate `Cache-Control` header', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeUndefined(); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and don\'t generate `Cache-Control` header', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeUndefined(); + }); + }); + + describe("should use cacheControl option when cacheImmutable is false even for immutable assets", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: 1000000 }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl option', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=1000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl option (not immutable)', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=1000", + ); + }); + }); + + describe("should use cacheControl string option when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: "max-age=500" }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl string option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe("max-age=500"); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl string option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe("max-age=500"); + }); + }); + + describe("should use cacheControl object option when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: { maxAge: 2000000 } }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl object option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=2000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl object option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=2000", + ); + }); + }); + + describe("should use cacheControl object option (with only immutable: true) when cacheImmutable is false, and add 'immutable' to Cache-Control header", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: { immutable: true } }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl object option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000, immutable", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl object option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000, immutable", + ); + }); + }); + + describe("should use cacheControl object option with explicit immutable false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheControl: { maxAge: 3000000, immutable: false } }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header without immutable when explicitly set to false', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=3000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header without immutable when explicitly set to false', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000, immutable", + ); + }); + }); + + describe("should use cacheControl boolean option when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: true }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl boolean option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl boolean option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000", + ); + }); + }); + + describe("should use cacheControl number option when cacheImmutable is false without immutable", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: 5000000 }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl number option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=5000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl number option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=5000", + ); + }); + }); }); }); }); diff --git a/test/utils/setupHooks.test.js b/test/utils/setupHooks.test.js index 8aaaa263f..92f437aef 100644 --- a/test/utils/setupHooks.test.js +++ b/test/utils/setupHooks.test.js @@ -12,6 +12,7 @@ describe("setupHooks", () => { const loggerInfo = jest.fn(); const loggerWarn = jest.fn(); const loggerError = jest.fn(); + const colorSupport = jest.fn(); let nextTick; const cb1 = jest.fn(); @@ -22,6 +23,11 @@ describe("setupHooks", () => { context = { options: {}, compiler: { + webpack: { + cli: { + isColorSupported: colorSupport, + }, + }, hooks: { watchRun: { tap: watchRunHook, @@ -126,12 +132,22 @@ describe("setupHooks", () => { it("handles multi compiler", () => { context.compiler.compilers = [ { + webpack: { + cli: { + isColorSupported: colorSupport, + }, + }, options: { name: "comp1", stats: {}, }, }, { + webpack: { + cli: { + isColorSupported: colorSupport, + }, + }, options: { name: "comp2", stats: {}, diff --git a/types/index.d.ts b/types/index.d.ts index 140425b45..df53752f7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -94,22 +94,22 @@ export = wdm; * @property {boolean=} lastModified options to generate last modified header * @property {(boolean | number | string | { maxAge?: number, immutable?: boolean })=} cacheControl options to generate cache headers * @property {boolean=} cacheImmutable is cache immutable + * @property {boolean=} forwardError forward error to next middleware */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @callback Middleware - * @param {RequestInternal} req - * @param {ResponseInternal} res - * @param {NextFunction} next + * @param {RequestInternal} req request + * @param {ResponseInternal} res response + * @param {NextFunction} next next function * @returns {Promise} */ /** @typedef {import("./utils/getFilenameFromUrl").Extra} Extra */ /** * @callback GetFilenameFromUrl - * @param {string} url - * @param {Extra=} extra - * @returns {string | undefined} + * @param {string} url request URL + * @returns {{ filename: string, extra: Extra } | undefined} a filename with additional information, or `undefined` if nothing is found */ /** * @callback WaitUntilValid @@ -153,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< @@ -161,6 +162,7 @@ declare function wdm< >( compiler: Compiler | MultiCompiler, options?: Options | undefined, + isPlugin?: boolean, ): API; declare namespace wdm { export { @@ -225,17 +227,19 @@ declare namespace wdm { /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal + * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false * @returns {HapiPlugin} hapi wrapper */ 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,12 +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 + * @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< @@ -258,6 +264,7 @@ declare function honoWrapper< >( compiler: Compiler | MultiCompiler, options?: Options | undefined, + usePlugin?: boolean | undefined, ): (ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise | void; type Schema = import("schema-utils/declarations/validate").Schema; type Compiler = import("webpack").Compiler; @@ -450,6 +457,10 @@ type Options< * is cache immutable */ cacheImmutable?: boolean | undefined; + /** + * forward error to next middleware + */ + forwardError?: boolean | undefined; }; type Middleware< RequestInternal extends IncomingMessage = import("http").IncomingMessage, @@ -460,10 +471,12 @@ type Middleware< next: NextFunction, ) => Promise; type Extra = import("./utils/getFilenameFromUrl").Extra; -type GetFilenameFromUrl = ( - url: string, - extra?: Extra | undefined, -) => string | undefined; +type GetFilenameFromUrl = (url: string) => + | { + filename: string; + extra: Extra; + } + | undefined; type WaitUntilValid = (callback: Callback) => any; type Invalidate = (callback: Callback) => any; type Close = (callback: (err: Error | null | undefined) => void) => any; diff --git a/types/middleware.d.ts b/types/middleware.d.ts index cce5820ac..2be44b1de 100644 --- a/types/middleware.d.ts +++ b/types/middleware.d.ts @@ -25,6 +25,8 @@ declare namespace wrapper { IncomingMessage, ServerResponse, NormalizedHeaders, + FilenameError, + Extra, ReadStream, }; } @@ -50,4 +52,6 @@ type NextFunction = import("./index.js").NextFunction; type IncomingMessage = import("./index.js").IncomingMessage; type ServerResponse = import("./index.js").ServerResponse; type NormalizedHeaders = import("./index.js").NormalizedHeaders; +type FilenameError = import("./utils/getFilenameFromUrl.js").FilenameError; +type Extra = import("./utils/getFilenameFromUrl.js").Extra; type ReadStream = import("fs").ReadStream; diff --git a/types/utils/getFilenameFromUrl.d.ts b/types/utils/getFilenameFromUrl.d.ts index be28a0ae0..2385f8646 100644 --- a/types/utils/getFilenameFromUrl.d.ts +++ b/types/utils/getFilenameFromUrl.d.ts @@ -1,24 +1,10 @@ export = getFilenameFromUrl; -/** - * @typedef {object} Extra - * @property {import("fs").Stats=} stats stats - * @property {number=} errorCode error code - * @property {boolean=} immutable true when immutable, otherwise false - */ -/** - * decodeURIComponent. - * - * Allows V8 to only deoptimize this fn instead of all of send(). - * @param {string} input - * @returns {string} - */ /** * @template {IncomingMessage} Request * @template {ServerResponse} Response * @param {import("../index.js").FilledContext} context context * @param {string} url url - * @param {Extra=} extra extra - * @returns {string | undefined} filename + * @returns {{ filename: string, extra: Extra } | undefined} result of get filename from url */ declare function getFilenameFromUrl< Request extends IncomingMessage, @@ -26,10 +12,34 @@ declare function getFilenameFromUrl< >( context: import("../index.js").FilledContext, url: string, - extra?: Extra | undefined, -): string | undefined; +): + | { + filename: string; + extra: Extra; + } + | undefined; declare namespace getFilenameFromUrl { - export { IncomingMessage, ServerResponse, Extra }; + export { FilenameError, IncomingMessage, ServerResponse, Extra }; +} +/** + * @typedef {object} Extra + * @property {import("fs").Stats} stats stats + * @property {boolean=} immutable true when immutable, otherwise false + */ +/** + * decodeURIComponent. + * + * Allows V8 to only deoptimize this fn instead of all of send(). + * @param {string} input + * @returns {string} + */ +declare class FilenameError extends Error { + /** + * @param {string} message message + * @param {number=} code error code + */ + constructor(message: string, code?: number | undefined); + code: number | undefined; } type IncomingMessage = import("../index.js").IncomingMessage; type ServerResponse = import("../index.js").ServerResponse; @@ -37,11 +47,7 @@ type Extra = { /** * stats */ - stats?: import("fs").Stats | undefined; - /** - * error code - */ - errorCode?: number | undefined; + stats: import("fs").Stats; /** * true when immutable, otherwise false */ 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 {