diff --git a/examples/general/proxy-advanced/webpack.config.js b/examples/general/proxy-advanced/webpack.config.js index 215ab84a01..e573112ea5 100644 --- a/examples/general/proxy-advanced/webpack.config.js +++ b/examples/general/proxy-advanced/webpack.config.js @@ -8,19 +8,24 @@ module.exports = setup({ context: __dirname, entry: "./app.js", devServer: { - proxy: { - "/api": { + port: 8080, + static: { + directory: ".", + }, + proxy: [ + { + context: "/api/nope", + router: () => "http://localhost:8080", + pathRewrite: () => "/bypass.html", + }, + { + context: "/api", target: "http://jsonplaceholder.typicode.com/", changeOrigin: true, pathRewrite: { "^/api": "", }, - bypass(req) { - if (req.url === "/api/nope") { - return "/bypass.html"; - } - }, }, - }, + ], }, }); diff --git a/examples/proxy/webpack.config.js b/examples/proxy/webpack.config.js index 0b550270a2..ad87568081 100644 --- a/examples/proxy/webpack.config.js +++ b/examples/proxy/webpack.config.js @@ -29,10 +29,11 @@ module.exports = setup({ onBeforeSetupMiddleware: async () => { await listenProxyServer(); }, - proxy: { - "/proxy": { + proxy: [ + { + context: "/proxy", target: "http://localhost:5000", }, - }, + ], }, }); diff --git a/lib/Server.js b/lib/Server.js index 6458f9ffa7..93bc098643 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -3,7 +3,6 @@ const os = require("node:os"); const path = require("node:path"); const url = require("node:url"); -const util = require("node:util"); const fs = require("graceful-fs"); const ipaddr = require("ipaddr.js"); const { validate } = require("schema-utils"); @@ -138,14 +137,7 @@ const schema = require("./options.json"); */ /** - * @callback ByPass - * @param {Request} req - * @param {Response} res - * @param {ProxyConfigArrayItem} proxyConfig - */ - -/** - * @typedef {{ path?: HttpProxyMiddlewareOptionsFilter | undefined, context?: HttpProxyMiddlewareOptionsFilter | undefined } & { bypass?: ByPass } & HttpProxyMiddlewareOptions } ProxyConfigArrayItem + * @typedef {{ path?: HttpProxyMiddlewareOptionsFilter | undefined, context?: HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions } ProxyConfigArrayItem */ /** @@ -2148,29 +2140,13 @@ class Server { * @returns {RequestHandler | undefined} request handler */ const getProxyMiddleware = (proxyConfig) => { - // It is possible to use the `bypass` method without a `target` or `router`. - // However, the proxy middleware has no use in this case, and will fail to instantiate. - if (proxyConfig.target) { - const context = proxyConfig.context || proxyConfig.path; - - return createProxyMiddleware({ - ...proxyConfig, - pathFilter: /** @type {string} */ (context), - }); - } + const context = + proxyConfig.context || proxyConfig.path || proxyConfig.pathFilter; - if (proxyConfig.router) { - return createProxyMiddleware(proxyConfig); - } - - // TODO improve me after drop `bypass` to always generate error when configuration is bad - if (!proxyConfig.bypass) { - util.deprecate( - () => {}, - `Invalid proxy configuration:\n\n${JSON.stringify(proxyConfig, null, 2)}\n\nThe use of proxy object notation as proxy routes has been removed.\nPlease use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options`, - "DEP_WEBPACK_DEV_SERVER_PROXY_ROUTES_ARGUMENT", - )(); - } + return createProxyMiddleware({ + ...proxyConfig, + pathFilter: /** @type {string} */ (context), + }); }; /** @@ -2236,40 +2212,7 @@ class Server { } } - // - Check if we have a bypass function defined - // - In case the bypass function is defined we'll retrieve the - // bypassUrl from it otherwise bypassUrl would be null - // TODO remove in the next major in favor `context` and `router` options - const isByPassFuncDefined = typeof proxyConfig.bypass === "function"; - if (isByPassFuncDefined) { - util.deprecate( - () => {}, - "Using the 'bypass' option is deprecated. Please use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options", - "DEP_WEBPACK_DEV_SERVER_PROXY_BYPASS_ARGUMENT", - )(); - } - const bypassUrl = isByPassFuncDefined - ? await /** @type {ByPass} */ (proxyConfig.bypass)( - req, - res, - proxyConfig, - ) - : null; - - if (typeof bypassUrl === "boolean") { - // skip the proxy - res.statusCode = 404; - req.url = ""; - next(); - } else if (typeof bypassUrl === "string") { - // byPass to that url - req.url = bypassUrl; - next(); - } else if (proxyMiddleware) { - return proxyMiddleware(req, res, next); - } else { - next(); - } + return proxyMiddleware(req, res, next); }; middlewares.push({ diff --git a/migration-v6.md b/migration-v6.md index e99a494b70..f5aa4c0bbb 100644 --- a/migration-v6.md +++ b/migration-v6.md @@ -5,6 +5,7 @@ This document serves as a migration guide for `webpack-dev-server@6.0.0`. ## ⚠ Breaking Changes - Minimum supported `Node.js` version is `20.9.0`. +- Minimum supported `webpack` version is `5.101.0`. - Support for **SockJS** in the WebSocket transport has been removed. Now, only **native WebSocket** is supported, or **custom** client and server implementations can be used. - The options for passing to the `proxy` have changed. Please refer to the [http-proxy-middleware migration guide](https://github.com/chimurai/http-proxy-middleware/blob/master/MIGRATION.md) for details. - Remove support for the spdy server type. Use the http2 server type instead; however, since Express does not work correctly with it, a custom server (e.g., Connect or Hono) should be used. @@ -51,3 +52,46 @@ This document serves as a migration guide for `webpack-dev-server@6.0.0`. ```js const ip = Server.findIp("v4", true); ``` + +- The bypass function in the proxy configuration was removed. Use the `pathFilter` and `router` for similar functionality. See the example below. + + v4: + + ```js + module.exports = { + // ... + devServer: { + proxy: [ + { + context: "/api", + bypass(req, res, proxyOptions) { + if (req.url.startsWith("/api/special")) { + return "/special.html"; + } + }, + }, + ], + }, + }; + ``` + + v5: + + ```js + module.exports = { + // ... + devServer: { + proxy: [ + { + pathFilter: "/api/special", + router: () => "http://localhost:3000", // Original Server + pathRewrite: () => "/special.html", + }, + ], + }, + }; + ``` + + When `bypass` was used and that function returned a boolean, it would automatically result in a `404` request. This can’t be achieved in a similar way now, or, if it returned a string, you can do what was done in the example above. + + `bypass` also allowed sending data; this can no longer be done. If you really need to do it, you’d have to create a new route in the proxy that sends the same data, or alternatively create a new route on the main server and, following the example above, send the data you wanted. diff --git a/test/server/proxy-option.test.js b/test/server/proxy-option.test.js index deb99e1ba9..76865bc1e9 100644 --- a/test/server/proxy-option.test.js +++ b/test/server/proxy-option.test.js @@ -1,7 +1,6 @@ "use strict"; const path = require("node:path"); -const util = require("node:util"); const express = require("express"); const request = require("supertest"); const webpack = require("webpack"); @@ -19,51 +18,14 @@ const proxyOptionPathsAsProperties = [ target: `http://localhost:${port1}`, }, { - context: "/api/proxy2", + path: "/api/proxy2", target: `http://localhost:${port2}`, pathRewrite: { "^/api": "" }, }, { - context: "/foo", - bypass(req) { - if (/\.html$/.test(req.path || req.url)) { - return "/index.html"; - } - - return null; - }, - }, - { - context: "proxyfalse", - bypass(req) { - if (/\/proxyfalse$/.test(req.path || req.url)) { - return false; - } - }, - }, - { - context: "/proxy/async", - bypass(req, res) { - if (/\/proxy\/async$/.test(req.path || req.url)) { - return new Promise((resolve) => { - setTimeout(() => { - res.end("proxy async response"); - resolve(true); - }, 10); - }); - } - }, - }, - { - context: "/bypass-with-target", - target: `http://localhost:${port1}`, - changeOrigin: true, - secure: false, - bypass(req) { - if (/\.(html)$/i.test(req.path || req.url)) { - return req.url; - } - }, + pathFilter: ["/foo/*.html", "/baz/*.html", "/bypass-with-target/*.html"], + pathRewrite: () => "/index.html", + router: () => `http://localhost:${port3}`, }, ]; @@ -77,7 +39,7 @@ const proxyOption = [ let maxServerListeners = 0; const proxyOptionOfArray = [ { context: "/proxy1", target: `http://localhost:${port1}` }, - function proxy(req, res, next) { + function proxy(req) { if (req) { const socket = req.socket || req.connection; const server = socket ? socket.server : null; @@ -92,19 +54,6 @@ const proxyOptionOfArray = [ context: "/api/proxy2", target: `http://localhost:${port2}`, pathRewrite: { "^/api": "" }, - bypass: () => { - if (req) { - const resolveUrl = new URL(req.url, `http://${req.headers.host}`); - const params = new URLSearchParams(resolveUrl.search); - const foo = params.get("foo"); - - if (foo) { - res.end(`foo+${next.name}+${typeof next}`); - - return false; - } - } - }, }; }, ]; @@ -167,6 +116,9 @@ describe("proxy option", () => { proxyApp1.get("/api", (req, res) => { res.send("api response from proxy1"); }); + proxyApp1.get("/index.html", (req, res) => { + res.send("Hello"); + }); proxyApp2.get("/proxy2", (req, res) => { res.send("from proxy2"); }); @@ -247,68 +199,48 @@ describe("proxy option", () => { }); }); - describe("bypass", () => { - it("should log deprecation warning when bypass is used", async () => { - const utilSpy = jest.spyOn(util, "deprecate"); - - const response = await req.get("/foo/bar.html"); - - expect(response.status).toBe(200); - expect(response.text).toContain("Hello"); - - const lastCall = utilSpy.mock.calls[utilSpy.mock.calls.length - 1]; - - expect(lastCall[1]).toBe( - "Using the 'bypass' option is deprecated. Please use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options", - ); - expect(lastCall[2]).toBe( - "DEP_WEBPACK_DEV_SERVER_PROXY_BYPASS_ARGUMENT", - ); - - utilSpy.mockRestore(); - }); - - it("can rewrite a request path", async () => { + describe("pathFilter and pathRewrite", () => { + it("should rewrite matching paths using pathFilter", async () => { const response = await req.get("/foo/bar.html"); expect(response.status).toBe(200); expect(response.text).toContain("Hello"); }); - it("can rewrite a request path regardless of the target defined a bypass option", async () => { + it("should rewrite paths using pathRewrite function", async () => { const response = await req.get("/baz/hoge.html"); expect(response.status).toBe(200); expect(response.text).toContain("Hello"); }); - it("should pass through a proxy when a bypass function returns null", async () => { + it("should proxy requests that don't match pathFilter", async () => { const response = await req.get("/foo.js"); expect(response.status).toBe(200); expect(response.text).toContain("Hey"); }); - it("should not pass through a proxy when a bypass function returns false", async () => { - const response = await req.get("/proxyfalse"); + it("should serve static files when not matching proxy rules", async () => { + const response = await req.get("/index.html"); - expect(response.status).toBe(404); + expect(response.status).toBe(200); + expect(response.text).toContain("Hello"); }); - it("should wait if bypass returns promise", async () => { - const response = await req.get("/proxy/async"); + it("should return 404 for unmatched paths", async () => { + const response = await req.get("/proxyfalse"); - expect(response.status).toBe(200); - expect(response.text).toContain("proxy async response"); + expect(response.status).toBe(404); }); - it("should work with the 'target' option", async () => { + it("should handle pathFilter with router option", async () => { const response = await req.get("/bypass-with-target/foo.js"); expect(response.status).toBe(404); }); - it("should work with the 'target' option #2", async () => { + it("should rewrite matching pathFilter patterns with router", async () => { const response = await req.get("/bypass-with-target/index.html"); expect(response.status).toBe(200); @@ -498,13 +430,6 @@ describe("proxy option", () => { expect(response.text).toContain("from proxy2"); }); - it("should allow req, res, and next", async () => { - const response = await req.get("/api/proxy2?foo=true"); - - expect(response.statusCode).toBe(200); - expect(response.text).toBe("foo+next+function"); - }); - it("should not exist multiple close events registered", async () => { expect(maxServerListeners).toBeLessThanOrEqual(1); }); diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts index 5ba932ce6e..dd895e2e3c 100644 --- a/types/lib/Server.d.ts +++ b/types/lib/Server.d.ts @@ -1466,7 +1466,6 @@ declare namespace Server { ClientConnection, WebSocketServer, WebSocketServerImplementation, - ByPass, ProxyConfigArrayItem, ProxyConfigArray, OpenApp, @@ -1656,16 +1655,9 @@ type WebSocketServerImplementation = { implementation: WebSocketServer; clients: ClientConnection[]; }; -type ByPass = ( - req: Request, - res: Response, - proxyConfig: ProxyConfigArrayItem, -) => any; type ProxyConfigArrayItem = { path?: HttpProxyMiddlewareOptionsFilter | undefined; context?: HttpProxyMiddlewareOptionsFilter | undefined; -} & { - bypass?: ByPass; } & HttpProxyMiddlewareOptions; type ProxyConfigArray = ( | ProxyConfigArrayItem