From 1a93c9253f87c10137df5bee8944ac4ab09036df Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Thu, 12 Sep 2024 16:48:47 +0800 Subject: [PATCH 1/4] refactor: clean dev server code --- packages/rspack-dev-server/src/alias.ts | 34 ++ packages/rspack-dev-server/src/server.ts | 378 +----------------- .../tests/fixtures/reload-config/main.css | 1 - 3 files changed, 56 insertions(+), 357 deletions(-) create mode 100644 packages/rspack-dev-server/src/alias.ts delete mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/main.css diff --git a/packages/rspack-dev-server/src/alias.ts b/packages/rspack-dev-server/src/alias.ts new file mode 100644 index 00000000000..54c6f49dfb7 --- /dev/null +++ b/packages/rspack-dev-server/src/alias.ts @@ -0,0 +1,34 @@ +const RESOLVER_MAP: Record = {}; +export const addResolveAlias = ( + name: string, + aliasMap: Record +) => { + if (RESOLVER_MAP[name]) { + throw new Error(`Should not add resolve alias to ${name} again.`); + } + const m = require.cache[require.resolve(name)]; + if (!m) { + throw new Error("Failed to resolve webpack-dev-server."); + } + RESOLVER_MAP[name] = m.require.resolve; + m.require.resolve = ((id: string, options?: any) => + aliasMap[id] || + RESOLVER_MAP[name]!.apply(m.require, [ + id, + options + ])) as typeof require.resolve; +}; + +export const removeResolveAlias = (name: string) => { + if (!RESOLVER_MAP[name]) { + throw new Error(`Should add resolve alias to ${name} before removing.`); + } + const m = require.cache[require.resolve(name)]; + if (!m) { + throw new Error("Failed to resolve webpack-dev-server"); + } + if (RESOLVER_MAP[name]) { + m.require.resolve = RESOLVER_MAP[name]!; + delete RESOLVER_MAP[name]; + } +}; diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index 000e95b80d8..e9835f6323e 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -13,23 +13,31 @@ import type { Server } from "node:http"; import type { Socket } from "node:net"; import { type Compiler, MultiCompiler } from "@rspack/core"; import type { FSWatcher } from "chokidar"; -import rdm from "webpack-dev-middleware"; -import WebpackDevServer, { - type OverlayMessageOptions -} from "webpack-dev-server"; +import WebpackDevServer from "webpack-dev-server"; // @ts-ignore 'package.json' is not under 'rootDir' import { version } from "../package.json"; +import { addResolveAlias } from "./alias"; import type { DevServer, ResolvedDevServer } from "./config"; import { applyDevServerPatch } from "./patch"; +// override +addResolveAlias("webpack-dev-server", { + "../client/clients/SockJSClient": require.resolve( + "webpack-dev-server/client/clients/SockJSClient" + ), + "../client/clients/WebSocketClient": require.resolve( + "webpack-dev-server/client/clients/WebSocketClient" + ), + "../client/index.js": require.resolve("@rspack/dev-server/client/index"), + "webpack/hot/only-dev-server": require.resolve( + "@rspack/core/hot/only-dev-server" + ), + "webpack/hot/dev-server": require.resolve("@rspack/core/hot/dev-server"), + webpack: require.resolve("@rspack/core") +}); applyDevServerPatch(); -const encodeOverlaySettings = (setting: OverlayMessageOptions | undefined) => - typeof setting === "function" - ? encodeURIComponent(setting.toString()) - : setting; - const getFreePort = async function getFreePort(port: string, host: string) { if (typeof port !== "undefined" && port !== null && port !== "auto") { return port; @@ -80,69 +88,7 @@ export class RspackDevServer extends WebpackDevServer { super(options, compiler as any); } - private override getClientTransport(): string { - // WARNING: we can't use `super.getClientTransport`, - // because we doesn't had same directory structure. - let clientImplementation: string | undefined; - let clientImplementationFound = true; - const isKnownWebSocketServerImplementation = - this.options.webSocketServer && - typeof this.options.webSocketServer.type === "string" && - (this.options.webSocketServer.type === "ws" || - this.options.webSocketServer.type === "sockjs"); - - let clientTransport: string | undefined; - - if (this.options.client) { - if (typeof this.options.client.webSocketTransport !== "undefined") { - clientTransport = this.options.client.webSocketTransport; - } else if (isKnownWebSocketServerImplementation) { - // @ts-expect-error: TS cannot infer webSocketServer is narrowed - clientTransport = this.options.webSocketServer.type; - } else { - clientTransport = "ws"; - } - } else { - clientTransport = "ws"; - } - - switch (typeof clientTransport) { - case "string": - // could be 'sockjs', 'ws', or a path that should be required - if (clientTransport === "sockjs") { - clientImplementation = require.resolve( - "webpack-dev-server/client/clients/SockJSClient" - ); - } else if (clientTransport === "ws") { - clientImplementation = require.resolve( - "webpack-dev-server/client/clients/WebSocketClient" - ); - } else { - try { - clientImplementation = require.resolve(clientTransport); - } catch (e) { - clientImplementationFound = false; - } - } - break; - default: - clientImplementationFound = false; - } - if (!clientImplementationFound) { - throw new Error( - `${ - !isKnownWebSocketServerImplementation - ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. " - : "" - }client.webSocketTransport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class ` - ); - } - - // @ts-expect-error - return clientImplementation; - } - - override async initialize() { + async initialize() { const compilers = this.compiler instanceof MultiCompiler ? this.compiler.compilers @@ -158,22 +104,6 @@ export class RspackDevServer extends WebpackDevServer { ); } - const HMRPluginExists = compiler.options.plugins.find( - p => p?.constructor === compiler.webpack.HotModuleReplacementPlugin - ); - - if (HMRPluginExists) { - this.logger.warn( - `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.` - ); - } else { - // Apply the HMR plugin - const plugin = new compiler.webpack.HotModuleReplacementPlugin(); - - plugin.apply(compiler); - } - - // Apply modified version of `ansi-html-community` compiler.options.resolve.alias = { "ansi-html-community": path.resolve(__dirname, "./ansiHTML"), ...compiler.options.resolve.alias @@ -181,276 +111,12 @@ export class RspackDevServer extends WebpackDevServer { } } - if (this.options.webSocketServer) { - for (const compiler of compilers) { - this.addAdditionalEntries(compiler); - new compiler.webpack.ProvidePlugin({ - __webpack_dev_server_client__: this.getClientTransport() - }).apply(compiler); - } + if (typeof this.options.client === "object") { + // TODO: support progress plugin event + this.options.client.progress = false; } - // @ts-expect-error: `setupHooks` is private function in base class. - this.setupHooks(); - // @ts-expect-error: `setupApp` is private function in base class. - this.setupApp(); - // @ts-expect-error: `setupHostHeaderCheck` is private function in base class. - this.setupHostHeaderCheck(); - this.setupDevMiddleware(); - // @ts-expect-error: `setupBuiltInRoutes` is private function in base class. - this.setupBuiltInRoutes(); - // @ts-expect-error: `setupWatchFiles` is private function in base class. - this.setupWatchFiles(); - // @ts-expect-error: `setupWatchStaticFiles` is private function in base class. - this.setupWatchStaticFiles(); - this.setupMiddlewares(); - // @ts-expect-error: `createServer` is private function in base class. - this.createServer(); - - if (this.options.setupExitSignals) { - const signals = ["SIGINT", "SIGTERM"]; - - let needForceShutdown = false; - - for (const signal of signals) { - const listener = () => { - if (needForceShutdown) { - process.exit(); - } - - this.logger.info( - "Gracefully shutting down. To force exit, press ^C again. Please wait..." - ); - - needForceShutdown = true; - - this.stopCallback(() => { - if (typeof this.compiler.close === "function") { - this.compiler.close(() => { - process.exit(); - }); - } else { - process.exit(); - } - }); - }; - - // @ts-expect-error: `listeners` is private function in base class. - this.listeners.push({ name: signal, listener }); - - process.on(signal, listener); - } - } - - // Proxy WebSocket without the initial http request - // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade - // @ts-expect-error: `webSocketProxies` is private function in base class. - for (const webSocketProxy of this.webSocketProxies) { - this.server.on("upgrade", webSocketProxy.upgrade); - } - } - - private override setupDevMiddleware() { // @ts-expect-error - this.middleware = rdm(this.compiler, this.options.devMiddleware); - } - - private override setupMiddlewares() { - const middlewares: WebpackDevServer.Middleware[] = []; - for (const middleware of middlewares) { - if (typeof middleware === "function") { - // @ts-expect-error - this.app.use(middleware); - } else if (typeof middleware.path !== "undefined") { - // @ts-expect-error - this.app.use(middleware.path, middleware.middleware); - } else { - // @ts-expect-error - this.app.use(middleware.middleware); - } - } - - // @ts-expect-error - super.setupMiddlewares(); - } - - private override addAdditionalEntries(compiler: Compiler) { - const additionalEntries = []; - // @ts-expect-error - const isWebTarget = WebpackDevServer.isWebTarget(compiler); - - // TODO maybe empty client - if (this.options.client && isWebTarget) { - let webSocketURLStr = ""; - - if (this.options.webSocketServer) { - const webSocketURL = this.options.client - .webSocketURL as WebpackDevServer.WebSocketURL; - const webSocketServer = this.options.webSocketServer; - const searchParams = new URLSearchParams(); - - let protocol: string; - - // We are proxying dev server and need to specify custom `hostname` - if (typeof webSocketURL.protocol !== "undefined") { - protocol = webSocketURL.protocol; - } else { - protocol = this.options.server.type === "http" ? "ws:" : "wss:"; - } - - searchParams.set("protocol", protocol); - - if (typeof webSocketURL.username !== "undefined") { - searchParams.set("username", webSocketURL.username); - } - - if (typeof webSocketURL.password !== "undefined") { - searchParams.set("password", webSocketURL.password); - } - - let hostname: string; - - // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them - // TODO show warning about this - const isSockJSType = webSocketServer.type === "sockjs"; - - // We are proxying dev server and need to specify custom `hostname` - if (typeof webSocketURL.hostname !== "undefined") { - hostname = webSocketURL.hostname; - } - // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname` - else if ( - typeof webSocketServer.options?.host !== "undefined" && - !isSockJSType - ) { - hostname = webSocketServer.options.host; - } - // The `host` option is specified - else if (typeof this.options.host !== "undefined") { - hostname = this.options.host; - } - // The `port` option is not specified - else { - hostname = "0.0.0.0"; - } - - searchParams.set("hostname", hostname); - - let port: number | string; - - // We are proxying dev server and need to specify custom `port` - if (typeof webSocketURL.port !== "undefined") { - port = webSocketURL.port; - } - // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port` - else if ( - typeof webSocketServer.options?.port !== "undefined" && - !isSockJSType - ) { - port = webSocketServer.options.port; - } - // The `port` option is specified - else if (typeof this.options.port === "number") { - port = this.options.port; - } - // The `port` option is specified using `string` - else if ( - typeof this.options.port === "string" && - this.options.port !== "auto" - ) { - port = Number(this.options.port); - } - // The `port` option is not specified or set to `auto` - else { - port = "0"; - } - - searchParams.set("port", String(port)); - - let pathname = ""; - - // We are proxying dev server and need to specify custom `pathname` - if (typeof webSocketURL.pathname !== "undefined") { - pathname = webSocketURL.pathname; - } - // Web socket server works on custom `path` - else if ( - typeof webSocketServer.options?.prefix !== "undefined" || - typeof webSocketServer.options?.path !== "undefined" - ) { - pathname = - webSocketServer.options.prefix || webSocketServer.options.path; - } - - searchParams.set("pathname", pathname); - - const client = /** @type {ClientConfiguration} */ this.options.client; - - if (typeof client.logging !== "undefined") { - searchParams.set("logging", client.logging); - } - - if (typeof client.progress !== "undefined") { - searchParams.set("progress", String(client.progress)); - } - - if (typeof client.overlay !== "undefined") { - const overlayString = - typeof client.overlay === "boolean" - ? String(client.overlay) - : JSON.stringify({ - ...client.overlay, - errors: encodeOverlaySettings(client.overlay.errors), - warnings: encodeOverlaySettings(client.overlay.warnings), - runtimeErrors: encodeOverlaySettings( - client.overlay.runtimeErrors - ) - }); - - searchParams.set("overlay", overlayString); - } - - if (typeof client.reconnect !== "undefined") { - searchParams.set( - "reconnect", - typeof client.reconnect === "number" - ? String(client.reconnect) - : "10" - ); - } - - if (typeof this.options.hot !== "undefined") { - searchParams.set("hot", String(this.options.hot)); - } - - if (typeof this.options.liveReload !== "undefined") { - searchParams.set("live-reload", String(this.options.liveReload)); - } - - webSocketURLStr = searchParams.toString(); - } - - additionalEntries.push( - `${require.resolve( - "@rspack/dev-server/client/index" - )}?${webSocketURLStr}` - ); - } - - if (this.options.hot === "only") { - additionalEntries.push( - require.resolve("@rspack/core/hot/only-dev-server") - ); - } else if (this.options.hot) { - additionalEntries.push(require.resolve("@rspack/core/hot/dev-server")); - } - - const webpack = compiler.webpack; - - for (const additionalEntry of additionalEntries) { - new webpack.EntryPlugin(compiler.context, additionalEntry, { - name: undefined - }).apply(compiler); - } + await super.initialize(); } } diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css deleted file mode 100644 index 585e8f34510..00000000000 --- a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css +++ /dev/null @@ -1 +0,0 @@ -body { background-color: rgb(0, 0, 255); } \ No newline at end of file From f9d5e9d6db568c215213cd50330e85f0f91cc2ab Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 13 Sep 2024 14:50:19 +0800 Subject: [PATCH 2/4] refactor: clean dev server code --- packages/rspack-dev-server/src/server.ts | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index e9835f6323e..27d0db2bc72 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -17,25 +17,10 @@ import WebpackDevServer from "webpack-dev-server"; // @ts-ignore 'package.json' is not under 'rootDir' import { version } from "../package.json"; -import { addResolveAlias } from "./alias"; +import { addResolveAlias, removeResolveAlias } from "./alias"; import type { DevServer, ResolvedDevServer } from "./config"; import { applyDevServerPatch } from "./patch"; -// override -addResolveAlias("webpack-dev-server", { - "../client/clients/SockJSClient": require.resolve( - "webpack-dev-server/client/clients/SockJSClient" - ), - "../client/clients/WebSocketClient": require.resolve( - "webpack-dev-server/client/clients/WebSocketClient" - ), - "../client/index.js": require.resolve("@rspack/dev-server/client/index"), - "webpack/hot/only-dev-server": require.resolve( - "@rspack/core/hot/only-dev-server" - ), - "webpack/hot/dev-server": require.resolve("@rspack/core/hot/dev-server"), - webpack: require.resolve("@rspack/core") -}); applyDevServerPatch(); const getFreePort = async function getFreePort(port: string, host: string) { @@ -86,6 +71,7 @@ export class RspackDevServer extends WebpackDevServer { constructor(options: DevServer, compiler: Compiler | MultiCompiler) { super(options, compiler as any); + // override } async initialize() { @@ -119,4 +105,18 @@ export class RspackDevServer extends WebpackDevServer { // @ts-expect-error await super.initialize(); } + + // @ts-ignore + private override addAdditionalEntries(compiler: Compiler) { + addResolveAlias("webpack-dev-server", { + "../client/index.js": require.resolve("@rspack/dev-server/client/index"), + "webpack/hot/only-dev-server": require.resolve( + "@rspack/core/hot/only-dev-server" + ), + "webpack/hot/dev-server": require.resolve("@rspack/core/hot/dev-server") + }); + // @ts-expect-error + super.addAdditionalEntries(compiler); + removeResolveAlias("webpack-dev-server"); + } } From 533e0483bd39e4d951f2f6ead8029db358897d1b Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 13 Sep 2024 15:06:40 +0800 Subject: [PATCH 3/4] refactor: clean dev server code --- packages/rspack-dev-server/src/alias.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/rspack-dev-server/src/alias.ts b/packages/rspack-dev-server/src/alias.ts index 54c6f49dfb7..d5debd2f9a5 100644 --- a/packages/rspack-dev-server/src/alias.ts +++ b/packages/rspack-dev-server/src/alias.ts @@ -3,32 +3,34 @@ export const addResolveAlias = ( name: string, aliasMap: Record ) => { - if (RESOLVER_MAP[name]) { + const modulePath = require.resolve(name); + if (RESOLVER_MAP[modulePath]) { throw new Error(`Should not add resolve alias to ${name} again.`); } - const m = require.cache[require.resolve(name)]; + const m = require.cache[modulePath]; if (!m) { throw new Error("Failed to resolve webpack-dev-server."); } - RESOLVER_MAP[name] = m.require.resolve; + RESOLVER_MAP[modulePath] = m.require.resolve; m.require.resolve = ((id: string, options?: any) => aliasMap[id] || - RESOLVER_MAP[name]!.apply(m.require, [ + RESOLVER_MAP[modulePath]!.apply(m.require, [ id, options ])) as typeof require.resolve; }; export const removeResolveAlias = (name: string) => { - if (!RESOLVER_MAP[name]) { - throw new Error(`Should add resolve alias to ${name} before removing.`); + const modulePath = require.resolve(name); + if (!RESOLVER_MAP[modulePath]) { + return; } - const m = require.cache[require.resolve(name)]; + const m = require.cache[modulePath]; if (!m) { throw new Error("Failed to resolve webpack-dev-server"); } - if (RESOLVER_MAP[name]) { - m.require.resolve = RESOLVER_MAP[name]!; - delete RESOLVER_MAP[name]; + if (RESOLVER_MAP[modulePath]) { + m.require.resolve = RESOLVER_MAP[modulePath]!; + delete RESOLVER_MAP[modulePath]; } }; From 911033d24f81f98745a6ad9f28b2163cdf478a53 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 13 Sep 2024 15:16:41 +0800 Subject: [PATCH 4/4] refactor: clean dev server code --- packages/rspack-dev-server/src/server.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index 27d0db2bc72..610145b8562 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -115,8 +115,11 @@ export class RspackDevServer extends WebpackDevServer { ), "webpack/hot/dev-server": require.resolve("@rspack/core/hot/dev-server") }); - // @ts-expect-error - super.addAdditionalEntries(compiler); - removeResolveAlias("webpack-dev-server"); + try { + // @ts-expect-error + super.addAdditionalEntries(compiler); + } finally { + removeResolveAlias("webpack-dev-server"); + } } }