Skip to content

Commit

Permalink
fix: lazy compilation request handler should filter lazy-compilation …
Browse files Browse the repository at this point in the history
…request (#9166)

* fix: support custom server

* docs: add docs

* chore: make docs easier to read
  • Loading branch information
JSerFeng authored Feb 10, 2025
1 parent 2ac097f commit 2d2ee75
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 64 deletions.
21 changes: 20 additions & 1 deletion packages/rspack/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/// <reference types="node" />

import type { Abortable } from 'node:events';
import type { AddressInfo } from 'node:net';
import { AsyncParallelHook } from '@rspack/lite-tapable';
import { AsyncSeriesBailHook } from '@rspack/lite-tapable';
import * as binding from '@rspack/binding';
Expand Down Expand Up @@ -75,9 +76,9 @@ import { RawRuntimeChunkOptions } from '@rspack/binding';
import { Resolver as Resolver_2 } from './Resolver';
import { RspackOptionsNormalized as RspackOptionsNormalized_2 } from '.';
import type { SecureContextOptions } from 'node:tls';
import type { Server } from 'node:net';
import type { ServerOptions } from 'node:http';
import type { ServerResponse } from 'node:http';
import type { Socket } from 'node:net';
import { RawSourceMapDevToolPluginOptions as SourceMapDevToolPluginOptions } from '@rspack/binding';
import sources = require('../compiled/webpack-sources');
import { SyncBailHook } from '@rspack/lite-tapable';
Expand Down Expand Up @@ -10096,6 +10097,24 @@ type SafeParseSuccess<Output> = {
// @public (undocumented)
export type ScriptType = false | "text/javascript" | "module";

// @public (undocumented)
interface Server {
// (undocumented)
address(): AddressInfo;
// (undocumented)
close(callback: (err?: any) => void): void;
// (undocumented)
listen(listenOptions?: number | ListenOptions): void;
// (undocumented)
off(event: "request", callback: (req: IncomingMessage, res: ServerResponse) => void): void;
// (undocumented)
on(event: "request", callback: (req: IncomingMessage, res: ServerResponse) => void): void;
// (undocumented)
on(event: "connection", callback: (socket: Socket) => void): void;
// (undocumented)
on(event: "listening", callback: (err?: Error) => void): void;
}

// @public (undocumented)
type ServerOptionsHttps<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse = typeof ServerResponse> = SecureContextOptions & TlsOptions & ServerOptions<Request, Response>;

Expand Down
44 changes: 32 additions & 12 deletions packages/rspack/src/builtin-plugin/lazy-compilation/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@
* MIT License http://www.opensource.org/licenses/mit-license.php
* Author Tobias Koppers @sokra
*/

import type {
IncomingMessage,
ServerOptions as ServerOptionsImport,
ServerResponse
} from "node:http";
import type { AddressInfo, ListenOptions, Server, Socket } from "node:net";
import type { AddressInfo, ListenOptions, Socket } from "node:net";
import type { SecureContextOptions, TlsOptions } from "node:tls";

import type { Compiler } from "../..";

interface Server {
on(
event: "request",
callback: (req: IncomingMessage, res: ServerResponse) => void
): void;
on(event: "connection", callback: (socket: Socket) => void): void;
on(event: "listening", callback: (err?: Error) => void): void;

off(
event: "request",
callback: (req: IncomingMessage, res: ServerResponse) => void
): void;

address(): AddressInfo;
close(callback: (err?: any) => void): void;
listen(listenOptions?: number | ListenOptions): void;
}

export interface LazyCompilationDefaultBackendOptions {
/**
* A custom client script path.
Expand Down Expand Up @@ -82,16 +99,22 @@ const getBackend =
const listen =
typeof options.listen === "function"
? options.listen
: (server: Server) => {
let listen = options.listen;
if (typeof listen === "object" && !("port" in listen))
listen = { ...listen, port: undefined };
server.listen(listen);
};
: typeof options.server === "function" && !options.listen
? // if user offers custom server, no need to listen
() => {}
: (server: Server) => {
let { listen } = options;
if (typeof listen === "object" && !("port" in listen))
listen = { ...listen, port: undefined };
server.listen(listen as ListenOptions);
};

const protocol = options.protocol || (isHttps ? "https" : "http");

const requestListener = (req: any, res: ServerResponse) => {
const requestListener = (req: IncomingMessage, res: ServerResponse) => {
if (!req.url?.startsWith(prefix)) {
return;
}
const keys = req.url.slice(prefix.length).split("@");
req.socket.on("close", () => {
setTimeout(() => {
Expand Down Expand Up @@ -143,9 +166,6 @@ const getBackend =
});
if (isClosing) socket.destroy();
});
server.on("clientError", e => {
if (e.message !== "Server is disposing") logger.warn(e);
});
server.on("listening", (err: any) => {
if (err) return callback(err);
const addr = server.address() as AddressInfo;
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/cases/css/css-filename/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { test, expect } from "@/fixtures";
test("should update css with filename", async ({
page,
fileAction,
rspack
}) => {
await expect(page.locator("body")).toHaveCSS("color", "rgb(255, 0, 0)");
fileAction.updateFile("src/index.css", content =>
Expand Down
123 changes: 95 additions & 28 deletions website/docs/en/config/experiments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,27 +111,7 @@ export default {
type LazyCompilationOptions =
| boolean
| {
backend?: {
/**
* A custom client script path.
*/
client?: string;
/**
* Specifies where to listen to from the server.
*/
listen?: number | ListenOptions | ((server: Server) => void);
/**
* Specifies the protocol the client should use to connect to the server.
*/
protocol?: 'http' | 'https';
/**
* Specifies how to create the server handling the EventSource requests.
*/
server?:
| ServerOptionsImport<typeof IncomingMessage>
| ServerOptionsHttps<typeof IncomingMessage, typeof ServerResponse>
| (() => Server);
};
backend?: LazyCompilationBackendOptions;
/**
* Enable lazy compilation for entries.
*/
Expand Down Expand Up @@ -168,9 +148,95 @@ In addition, you can also configure a `test` parameter for more fine-grained con
The current lazy compilation aligns with the webpack implementation, **and is still in the experimental stage**. In some scenarios, lazy compilation might not work as expected, or the performance improvement may be insignificant.
:::

## experiments.lazyCompilation.backend

- **Type:** `LazyCompilationBackendOptions`

```ts
type LazyCompilationBackendOptions = {
/**
* Custom client script path.
*/
client?: string;
/**
* Specify the port or options to listen from the server.
*/
listen?: number | ListenOptions | ((server: Server) => void);
/**
* Specify the protocol used by the client.
*/
protocol?: 'http' | 'https';
/**
* Specify how to create a server that handles EventSource requests.
*/
server?:
| ServerOptionsImport<typeof IncomingMessage>
| ServerOptionsHttps<typeof IncomingMessage, typeof ServerResponse>
| (() => Server);
};
```

Enabling lazy compilation will start a separate server to handle requests from clients. You can customize the behavior of the server by configuring `lazyCompilation.backend`, and you can also provide your own server instance.

### lazyCompilation.backend.server

- **Type:**

```ts
type ServerOptions =
| ServerOptionsImport<typeof IncomingMessage>
| ServerOptionsHttps<typeof IncomingMessage, typeof ServerResponse>
| (() => Server);
```

```js title="rspack.config.js"
module.exports = {
experiments: {
lazyCompilation: {
backend: {
server() {
return yourServer;
},
},
},
},
};
```

You can provide a custom server instance to handle requests from clients for `lazy-compilation`. The provided server must meet the following interface requirements.

```ts
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { AddressInfo, ListenOptions, Socket } from 'node:net';

interface Server {
// Register callback function to handle requests
on(
event: 'request',
callback: (req: IncomingMessage, res: ServerResponse) => void,
): void;

// Register callback function for new client connections
on(event: 'connection', callback: (socket: Socket) => void): void;

// Register callback function for new client connections
on(event: 'listening', callback: (err?: Error) => void): void;

// Unregister registered callbacks
off(event: string, callback: () => void): void;

// Show address information of the server
address(): AddressInfo;

close(callback: (err?: any) => void): void;

listen(listenOption: number | ListenOption): void;
}
```

### lazyCompilation.backend.listen

- **Type:** `number | ListenOptions`
- **Type:** `number | ListenOptions | ((server: Server) => void)`

```ts
type ListenOptions = {
Expand All @@ -179,15 +245,16 @@ type ListenOptions = {
backlog?: number | undefined;
path?: string | undefined;
exclusive?: boolean | undefined;
readableAll?: boolean | undefined;
writableAll?: boolean | undefined;
/**
* @default false
*/
ipv6Only?: boolean | undefined;
readableAll?: boolean | null;
writableAll?: boolean | null;
ip6Only?: boolean | null;
};
```

Specify how the sever listens ,you can pass in object form listening configuration,such as specifying listening ports,you can also pass in functions control how listen is done.

When providing a custom server instance, you are responsible for calling the server's `listen` method to start the service, unless you explicitly configure a function forms` listen` option.

### Exclude HMR client

If you do not use Rspack's own dev server and instead use your own server as the dev server, you generally need to add another client modules in the entry configuration to enable capabilities such as HMR. It is best to exclude these client module from lazy compilation by configuring `test`.
Expand Down
Loading

2 comments on commit 2d2ee75

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented on 2d2ee75 Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2025-02-10 09af1d8) Current Change
10000_big_production-mode_disable-minimize + exec 37.7 s ± 622 ms 38.7 s ± 1.07 s +2.72 %
10000_development-mode + exec 1.87 s ± 37 ms 1.84 s ± 20 ms -1.80 %
10000_development-mode_hmr + exec 684 ms ± 18 ms 691 ms ± 4.1 ms +1.02 %
10000_production-mode + exec 2.31 s ± 63 ms 2.29 s ± 43 ms -0.64 %
10000_production-mode_persistent-cold + exec 2.45 s ± 74 ms 2.45 s ± 175 ms -0.02 %
10000_production-mode_persistent-hot + exec 1.64 s ± 26 ms 1.67 s ± 79 ms +1.65 %
arco-pro_development-mode + exec 1.72 s ± 90 ms 1.74 s ± 122 ms +0.81 %
arco-pro_development-mode_hmr + exec 389 ms ± 2.1 ms 388 ms ± 2.1 ms -0.15 %
arco-pro_production-mode + exec 3.62 s ± 212 ms 3.7 s ± 227 ms +2.17 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.67 s ± 262 ms 3.72 s ± 168 ms +1.26 %
arco-pro_production-mode_persistent-cold + exec 3.83 s ± 285 ms 3.8 s ± 161 ms -0.73 %
arco-pro_production-mode_persistent-hot + exec 2.36 s ± 60 ms 2.44 s ± 83 ms +3.29 %
arco-pro_production-mode_traverse-chunk-modules + exec 3.63 s ± 197 ms 3.7 s ± 202 ms +1.92 %
large-dyn-imports_development-mode + exec 2.12 s ± 39 ms 2.11 s ± 37 ms -0.32 %
large-dyn-imports_production-mode + exec 2.14 s ± 20 ms 2.16 s ± 51 ms +0.54 %
threejs_development-mode_10x + exec 1.53 s ± 28 ms 1.56 s ± 96 ms +2.02 %
threejs_development-mode_10x_hmr + exec 783 ms ± 13 ms 803 ms ± 12 ms +2.54 %
threejs_production-mode_10x + exec 5.22 s ± 186 ms 5.24 s ± 61 ms +0.40 %
threejs_production-mode_10x_persistent-cold + exec 5.31 s ± 155 ms 5.28 s ± 26 ms -0.51 %
threejs_production-mode_10x_persistent-hot + exec 4.53 s ± 315 ms 4.48 s ± 92 ms -1.01 %
10000_big_production-mode_disable-minimize + rss memory 8773 MiB ± 74.2 MiB 8691 MiB ± 35.2 MiB -0.94 %
10000_development-mode + rss memory 653 MiB ± 15.5 MiB 655 MiB ± 20.3 MiB +0.27 %
10000_development-mode_hmr + rss memory 1264 MiB ± 261 MiB 1300 MiB ± 145 MiB +2.81 %
10000_production-mode + rss memory 623 MiB ± 16.1 MiB 627 MiB ± 17.1 MiB +0.58 %
10000_production-mode_persistent-cold + rss memory 743 MiB ± 22.3 MiB 749 MiB ± 7.64 MiB +0.74 %
10000_production-mode_persistent-hot + rss memory 714 MiB ± 18.8 MiB 721 MiB ± 17.6 MiB +0.89 %
arco-pro_development-mode + rss memory 561 MiB ± 21.6 MiB 561 MiB ± 29.9 MiB -0.06 %
arco-pro_development-mode_hmr + rss memory 638 MiB ± 51.3 MiB 648 MiB ± 78.2 MiB +1.68 %
arco-pro_production-mode + rss memory 735 MiB ± 26.9 MiB 707 MiB ± 21.3 MiB -3.81 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 741 MiB ± 13.1 MiB 731 MiB ± 88.6 MiB -1.33 %
arco-pro_production-mode_persistent-cold + rss memory 857 MiB ± 36.2 MiB 826 MiB ± 40.7 MiB -3.60 %
arco-pro_production-mode_persistent-hot + rss memory 730 MiB ± 6.4 MiB 707 MiB ± 39.8 MiB -3.15 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 737 MiB ± 60.8 MiB 706 MiB ± 20.1 MiB -4.19 %
large-dyn-imports_development-mode + rss memory 647 MiB ± 9.25 MiB 639 MiB ± 7.49 MiB -1.22 %
large-dyn-imports_production-mode + rss memory 530 MiB ± 6.21 MiB 520 MiB ± 6.61 MiB -1.90 %
threejs_development-mode_10x + rss memory 551 MiB ± 25.1 MiB 543 MiB ± 15.4 MiB -1.37 %
threejs_development-mode_10x_hmr + rss memory 1124 MiB ± 189 MiB 1145 MiB ± 162 MiB +1.86 %
threejs_production-mode_10x + rss memory 829 MiB ± 36.8 MiB 822 MiB ± 35.2 MiB -0.87 %
threejs_production-mode_10x_persistent-cold + rss memory 967 MiB ± 35.8 MiB 963 MiB ± 31.1 MiB -0.50 %
threejs_production-mode_10x_persistent-hot + rss memory 868 MiB ± 38.6 MiB 852 MiB ± 61.1 MiB -1.78 %

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented on 2d2ee75 Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ecosystem CI detail: Open

suite result
modernjs ❌ failure
rspress ✅ success
rslib ✅ success
rsbuild ❌ failure
rsdoctor ❌ failure
examples ✅ success
devserver ✅ success
nuxt ✅ success

Please sign in to comment.