Skip to content

Commit

Permalink
feat: rspack-cli and devServer support multiCompiler (#1816)
Browse files Browse the repository at this point in the history
Co-authored-by: fengyu.shelby <fengyu.shelby@bytedance.com>
  • Loading branch information
JSerFeng and fengyu.shelby authored Feb 14, 2023
1 parent 62a61e1 commit 1c7ab6d
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 188 deletions.
7 changes: 7 additions & 0 deletions .changeset/clever-years-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rspack/core": patch
"@rspack/cli": patch
"@rspack/dev-server": patch
---

feat: rspack-cli and devServer support multiCompiler
12 changes: 10 additions & 2 deletions packages/rspack-cli/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { RspackCLI } from "../rspack-cli";
import { RspackCommand } from "../types";
import { commonOptions } from "../utils/options";
import { Stats } from "@rspack/core/src/stats";
import { Compiler } from "@rspack/core";
import MultiStats from "@rspack/core/src/multiStats";

export class BuildCommand implements RspackCommand {
async apply(cli: RspackCLI): Promise<void> {
Expand All @@ -28,7 +30,7 @@ export class BuildCommand implements RspackCommand {
createJsonStringifyStream = jsonExt.stringifyStream;
}

const callback = (error, stats: Stats) => {
const callback = (error, stats: Stats | MultiStats) => {
if (error) {
logger.error(error);
process.exit(2);
Expand All @@ -39,7 +41,13 @@ export class BuildCommand implements RspackCommand {
if (!compiler || !stats) {
return;
}
const statsOptions = compiler.options
const statsOptions = cli.isMultipleCompiler(compiler)
? {
children: compiler.compilers.map(compiler =>
compiler.options ? compiler.options.stats : undefined
)
}
: compiler.options
? compiler.options.stats
: undefined;
if (options.json && createJsonStringifyStream) {
Expand Down
65 changes: 60 additions & 5 deletions packages/rspack-cli/src/commands/serve.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { RspackCLI } from "../rspack-cli";
import { RspackDevServer } from "@rspack/dev-server";
import { RspackCommand } from "../types";
import { commonOptions, normalizeEnv } from "../utils/options";
import { commonOptions } from "../utils/options";
import { Compiler, DevServer } from "@rspack/core";
export class ServeCommand implements RspackCommand {
async apply(cli: RspackCLI): Promise<void> {
cli.program.command(
Expand All @@ -16,11 +17,65 @@ export class ServeCommand implements RspackCommand {
}
};
const compiler = await cli.createCompiler(rspackOptions, "development");
const server = new RspackDevServer(
compiler.options.devServer,
compiler
const compilers = cli.isMultipleCompiler(compiler)
? compiler.compilers
: [compiler];
const possibleCompilers = compilers.filter(
(compiler: Compiler) => compiler.options.devServer
);
await server.start();

const usedPorts: number[] = [];
const servers: RspackDevServer[] = [];

/**
* Webpack uses an Array of compilerForDevServer,
* however according to it's doc https://webpack.js.org/configuration/dev-server/#devserverhot
* It should use only the first one
*
* Choose the one for configure devServer
*/
const compilerForDevServer =
possibleCompilers.length > 0 ? possibleCompilers[0] : compilers[0];

/**
* Rspack relies on devServer.hot to enable HMR
*/
for (const compiler of compilers) {
const devServer = (compiler.options.devServer ??= {});
devServer.hot ??= true;
}

const result = (compilerForDevServer.options.devServer ??= {});
/**
* Enable this to tell Rspack that we need to enable React Refresh by default
*/
result.hot ??= true;

const devServerOptions = result as DevServer;
if (devServerOptions.port) {
const portNumber = Number(devServerOptions.port);

if (usedPorts.find(port => portNumber === port)) {
throw new Error(
"Unique ports must be specified for each devServer option in your rspack configuration. Alternatively, run only 1 devServer config using the --config-name flag to specify your desired config."
);
}

usedPorts.push(portNumber);
}

try {
const server = new RspackDevServer(devServerOptions, compiler);

await server.start();

servers.push(server);
} catch (error) {
const logger = cli.getLogger();
logger.error(error);

process.exit(2);
}
}
);
}
Expand Down
217 changes: 149 additions & 68 deletions packages/rspack-cli/src/rspack-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ import fs from "fs";
import { RspackCLIColors, RspackCLILogger, RspackCLIOptions } from "./types";
import { BuildCommand } from "./commands/build";
import { ServeCommand } from "./commands/serve";
import { rspack, RspackOptions, createCompiler } from "@rspack/core";
import {
RspackOptions,
MultiCompilerOptions,
createMultiCompiler,
createCompiler,
MultiCompiler,
Compiler,
rspack,
MultiRspackOptions
} from "@rspack/core";
import { normalizeEnv } from "./utils/options";
import { Mode } from "@rspack/core/src/config/mode";

const defaultConfig = "rspack.config.js";
const defaultEntry = "src/index.js";
type Callback<T> = <T>(err: Error, res?: T) => void;
Expand All @@ -19,15 +30,19 @@ export class RspackCLI {
this.colors = this.createColors();
this.program = yargs();
}
async createCompiler(options: RspackCLIOptions, rspackEnv: RspackEnv) {
async createCompiler(
options: RspackCLIOptions,
rspackEnv: RspackEnv
): Promise<Compiler | MultiCompiler> {
if (typeof options.nodeEnv === "string") {
process.env.NODE_ENV = options.nodeEnv;
} else {
process.env.NODE_ENV = rspackEnv;
}
let config = await this.loadConfig(options);
config = await this.buildConfig(config, options, rspackEnv);
const compiler = createCompiler(config);
// @ts-ignore
const compiler = rspack(config);
return compiler;
}
createColors(useColor?: boolean): RspackCLIColors {
Expand Down Expand Up @@ -71,85 +86,108 @@ export class RspackCLI {
}
}
async buildConfig(
item: any,
item: RspackOptions | MultiRspackOptions,
options: RspackCLIOptions,
rspackEnv: RspackEnv
) {
const isEnvProduction = rspackEnv === "production";
const isEnvDevelopment = rspackEnv === "development";

if (options.analyze) {
const { BundleAnalyzerPlugin } = await import("webpack-bundle-analyzer");
(item.plugins ??= []).push({
name: "rspack-bundle-analyzer",
apply(compiler) {
new BundleAnalyzerPlugin({
generateStatsFile: true
}).apply(compiler as any);
}
});
}
// auto set default mode if user config don't set it
if (!item.mode) {
item.mode = rspackEnv ?? "none";
}
// user parameters always has highest priority than default mode and config mode
if (options.mode) {
item.mode = options.mode;
}

// false is also a valid value for sourcemap, so don't override it
if (typeof item.devtool === "undefined") {
item.devtool = isEnvProduction ? "source-map" : "cheap-module-source-map";
}
item.builtins = {
...item.builtins,
minify: item.builtins?.minify ?? item.mode === "production"
};
): Promise<RspackOptions | MultiRspackOptions> {
const internalBuildConfig = async (item: RspackOptions) => {
const isEnvProduction = rspackEnv === "production";
const isEnvDevelopment = rspackEnv === "development";

// no emit assets when run dev server, it will use node_binding api get file content
if (typeof item.builtins.noEmitAssets === "undefined") {
item.builtins.noEmitAssets = false; // @FIXME memory fs currently cause problems for outputFileSystem, so we disable it temporarily
}
if (options.analyze) {
const { BundleAnalyzerPlugin } = await import(
"webpack-bundle-analyzer"
);
(item.plugins ??= []).push({
name: "rspack-bundle-analyzer",
apply(compiler) {
new BundleAnalyzerPlugin({
generateStatsFile: true
}).apply(compiler as any);
}
});
}
// auto set default mode if user config don't set it
if (!item.mode) {
item.mode = rspackEnv ?? "none";
}
// user parameters always has highest priority than default mode and config mode
if (options.mode) {
item.mode = options.mode as Mode;
}

// Tells webpack to set process.env.NODE_ENV to a given string value.
// optimization.nodeEnv uses DefinePlugin unless set to false.
// optimization.nodeEnv defaults to mode if set, else falls back to 'production'.
// See doc: https://webpack.js.org/configuration/optimization/#optimizationnodeenv
// See source: https://github.com/webpack/webpack/blob/8241da7f1e75c5581ba535d127fa66aeb9eb2ac8/lib/WebpackOptionsApply.js#L563

// When mode is set to 'none', optimization.nodeEnv defaults to false.
if (item.mode !== "none") {
item.builtins.define = {
// User defined `process.env.NODE_ENV` always has highest priority than default define
"process.env.NODE_ENV": JSON.stringify(item.mode),
...item.builtins.define
// false is also a valid value for sourcemap, so don't override it
if (typeof item.devtool === "undefined") {
item.devtool = isEnvProduction
? "source-map"
: "cheap-module-source-map";
}
item.builtins = {
...item.builtins,
minify: item.builtins?.minify ?? item.mode === "production"
};
}

item.output = {
...item.output,
publicPath: item.output?.publicPath ?? "/"
// no emit assets when run dev server, it will use node_binding api get file content
if (typeof item.builtins.noEmitAssets === "undefined") {
item.builtins.noEmitAssets = false; // @FIXME memory fs currently cause problems for outputFileSystem, so we disable it temporarily
}

// Tells webpack to set process.env.NODE_ENV to a given string value.
// optimization.nodeEnv uses DefinePlugin unless set to false.
// optimization.nodeEnv defaults to mode if set, else falls back to 'production'.
// See doc: https://webpack.js.org/configuration/optimization/#optimizationnodeenv
// See source: https://github.com/webpack/webpack/blob/8241da7f1e75c5581ba535d127fa66aeb9eb2ac8/lib/WebpackOptionsApply.js#L563

// When mode is set to 'none', optimization.nodeEnv defaults to false.
if (item.mode !== "none") {
item.builtins.define = {
// User defined `process.env.NODE_ENV` always has highest priority than default define
"process.env.NODE_ENV": JSON.stringify(item.mode),
...item.builtins.define
};
}

item.output = {
...item.output,
publicPath: item.output?.publicPath ?? "/"
};
if (typeof item.stats === "undefined") {
item.stats = { preset: "normal" };
} else if (typeof item.stats === "boolean") {
item.stats = item.stats ? { preset: "normal" } : { preset: "none" };
} else if (typeof item.stats === "string") {
item.stats = {
preset: item.stats as
| "normal"
| "none"
| "verbose"
| "errors-only"
| "errors-warnings"
};
}
if (this.colors.isColorSupported && !item.stats.colors) {
item.stats.colors = true;
}
return item;
};
if (typeof item.stats === "undefined") {
item.stats = { preset: "normal" };
} else if (typeof item.stats === "boolean") {
item.stats = item.stats ? { preset: "normal" } : { preset: "none" };
} else if (typeof item.stats === "string") {
item.stats = { preset: item.stats };
}
if (this.colors.isColorSupported && !item.stats.colors) {
item.stats.colors = true;

if (Array.isArray(item)) {
return Promise.all(item.map(internalBuildConfig));
} else {
return internalBuildConfig(item as RspackOptions);
}
return item;
}
async loadConfig(options: RspackCLIOptions): Promise<RspackOptions> {
async loadConfig(
options: RspackCLIOptions
): Promise<RspackOptions | MultiRspackOptions> {
let loadedConfig:
| undefined
| RspackOptions
| MultiRspackOptions
| ((
env: Record<string, any>,
argv: Record<string, any>
) => RspackOptions);
) => RspackOptions | MultiRspackOptions);
// if we pass config paras
if (options.config) {
const resolvedConfigPath = path.resolve(process.cwd(), options.config);
Expand Down Expand Up @@ -177,9 +215,52 @@ export class RspackCLI {
};
}
}

if (options.configName) {
const notFoundConfigNames: string[] = [];

// @ts-ignore
loadedConfig = options.configName.map((configName: string) => {
let found: RspackOptions | MultiRspackOptions | undefined;

if (Array.isArray(loadedConfig)) {
found = loadedConfig.find(options => options.name === configName);
} else {
found =
(loadedConfig as RspackOptions).name === configName
? (loadedConfig as RspackOptions)
: undefined;
}

if (!found) {
notFoundConfigNames.push(configName);
}

return found;
});

if (notFoundConfigNames.length > 0) {
this.getLogger().error(
notFoundConfigNames
.map(
configName =>
`Configuration with the name "${configName}" was not found.`
)
.join(" ")
);
process.exit(2);
}
}

if (typeof loadedConfig === "function") {
loadedConfig = loadedConfig(options.argv?.env, options.argv);
}
return loadedConfig;
}

isMultipleCompiler(
compiler: Compiler | MultiCompiler
): compiler is MultiCompiler {
return Boolean((compiler as MultiCompiler).compilers);
}
}
1 change: 1 addition & 0 deletions packages/rspack-cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface RspackCLIOptions {
argv?: Record<string, any>;
env?: Record<string, any>;
nodeEnv: string;
configName?: string[];
}

export interface RspackCommand {
Expand Down
Loading

0 comments on commit 1c7ab6d

Please sign in to comment.