@rspack/cli
Version:
CLI for rspack
1,207 lines • 54.9 kB
JavaScript
import node_path from "node:path";
import node_util from "node:util";
import { rspack } from "@rspack/core";
import node_fs from "node:fs";
import { createRequire } from "node:module";
import { fileURLToPath, pathToFileURL } from "node:url";
function toArr(any) {
return null == any ? [] : Array.isArray(any) ? any : [
any
];
}
function toVal(out, key, val, opts) {
var x, old = out[key], nxt = ~opts.string.indexOf(key) ? null == val || true === val ? "" : String(val) : "boolean" == typeof val ? val : ~opts.boolean.indexOf(key) ? "false" === val ? false : "true" === val || (out._.push((x = +val, 0 * x === 0) ? x : val), !!val) : (x = +val, 0 * x === 0) ? x : val;
out[key] = null == old ? nxt : Array.isArray(old) ? old.concat(nxt) : [
old,
nxt
];
}
function lib_default(args, opts) {
args = args || [];
opts = opts || {};
var k, arr, arg, name, val, out = {
_: []
};
var i = 0, j = 0, idx = 0, len = args.length;
const alibi = void 0 !== opts.alias;
const strict = void 0 !== opts.unknown;
const defaults = void 0 !== opts.default;
opts.alias = opts.alias || {};
opts.string = toArr(opts.string);
opts.boolean = toArr(opts.boolean);
if (alibi) for(k in opts.alias){
arr = opts.alias[k] = toArr(opts.alias[k]);
for(i = 0; i < arr.length; i++)(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
}
for(i = opts.boolean.length; i-- > 0;){
arr = opts.alias[opts.boolean[i]] || [];
for(j = arr.length; j-- > 0;)opts.boolean.push(arr[j]);
}
for(i = opts.string.length; i-- > 0;){
arr = opts.alias[opts.string[i]] || [];
for(j = arr.length; j-- > 0;)opts.string.push(arr[j]);
}
if (defaults) for(k in opts.default){
name = typeof opts.default[k];
arr = opts.alias[k] = opts.alias[k] || [];
if (void 0 !== opts[name]) {
opts[name].push(k);
for(i = 0; i < arr.length; i++)opts[name].push(arr[i]);
}
}
const keys = strict ? Object.keys(opts.alias) : [];
for(i = 0; i < len; i++){
arg = args[i];
if ("--" === arg) {
out._ = out._.concat(args.slice(++i));
break;
}
for(j = 0; j < arg.length && 45 === arg.charCodeAt(j); j++);
if (0 === j) out._.push(arg);
else if ("no-" === arg.substring(j, j + 3)) {
name = arg.substring(j + 3);
if (strict && !~keys.indexOf(name)) return opts.unknown(arg);
out[name] = false;
} else {
for(idx = j + 1; idx < arg.length && 61 !== arg.charCodeAt(idx); idx++);
name = arg.substring(j, idx);
val = arg.substring(++idx) || i + 1 === len || 45 === ("" + args[i + 1]).charCodeAt(0) || args[++i];
arr = 2 === j ? [
name
] : name;
for(idx = 0; idx < arr.length; idx++){
name = arr[idx];
if (strict && !~keys.indexOf(name)) return opts.unknown("-".repeat(j) + name);
toVal(out, name, idx + 1 < arr.length || val, opts);
}
}
}
if (defaults) {
for(k in opts.default)if (void 0 === out[k]) out[k] = opts.default[k];
}
if (alibi) for(k in out){
arr = opts.alias[k] || [];
while(arr.length > 0)out[arr.shift()] = out[k];
}
return out;
}
function removeBrackets(v) {
return v.replace(/[<[].+/, "").trim();
}
function findAllBrackets(v) {
const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
const res = [];
const parse = (match)=>{
let variadic = false;
let value = match[1];
if (value.startsWith("...")) {
value = value.slice(3);
variadic = true;
}
return {
required: match[0].startsWith("<"),
value,
variadic
};
};
let angledMatch;
while(angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v))res.push(parse(angledMatch));
let squareMatch;
while(squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v))res.push(parse(squareMatch));
return res;
}
function getMriOptions(options) {
const result = {
alias: {},
boolean: []
};
for (const [index, option] of options.entries()){
if (option.names.length > 1) result.alias[option.names[0]] = option.names.slice(1);
if (option.isBoolean) if (option.negated) {
if (!options.some((o, i)=>i !== index && o.names.some((name)=>option.names.includes(name)) && "boolean" == typeof o.required)) result.boolean.push(option.names[0]);
} else result.boolean.push(option.names[0]);
}
return result;
}
function findLongest(arr) {
return arr.sort((a, b)=>a.length > b.length ? -1 : 1)[0];
}
function padRight(str, length) {
return str.length >= length ? str : `${str}${" ".repeat(length - str.length)}`;
}
function camelcase(input) {
return input.replaceAll(/([a-z])-([a-z])/g, (_, p1, p2)=>p1 + p2.toUpperCase());
}
function setDotProp(obj, keys, val) {
let current = obj;
for(let i = 0; i < keys.length; i++){
const key = keys[i];
if (i === keys.length - 1) {
current[key] = val;
return;
}
if (null == current[key]) {
const nextKeyIsArrayIndex = +keys[i + 1] > -1;
current[key] = nextKeyIsArrayIndex ? [] : {};
}
current = current[key];
}
}
function setByType(obj, transforms) {
for (const key of Object.keys(transforms)){
const transform = transforms[key];
if (transform.shouldTransform) {
obj[key] = [
obj[key]
].flat();
if ("function" == typeof transform.transformFunction) obj[key] = obj[key].map(transform.transformFunction);
}
}
}
function getFileName(input) {
const m = /([^\\/]+)$/.exec(input);
return m ? m[1] : "";
}
function camelcaseOptionName(name) {
return name.split(".").map((v, i)=>0 === i ? camelcase(v) : v).join(".");
}
var CACError = class extends Error {
constructor(message){
super(message);
this.name = "CACError";
if ("function" != typeof Error.captureStackTrace) this.stack = new Error(message).stack;
}
};
var Option = class {
rawName;
description;
name;
names;
isBoolean;
required;
config;
negated;
constructor(rawName, description, config){
this.rawName = rawName;
this.description = description;
this.config = Object.assign({}, config);
rawName = rawName.replaceAll(".*", "");
this.negated = false;
this.names = removeBrackets(rawName).split(",").map((v)=>{
let name = v.trim().replace(/^-{1,2}/, "");
if (name.startsWith("no-")) {
this.negated = true;
name = name.replace(/^no-/, "");
}
return camelcaseOptionName(name);
}).sort((a, b)=>a.length > b.length ? 1 : -1);
this.name = this.names.at(-1);
if (this.negated && null == this.config.default) this.config.default = true;
if (rawName.includes("<")) this.required = true;
else if (rawName.includes("[")) this.required = false;
else this.isBoolean = true;
}
};
let runtimeProcessArgs;
let runtimeInfo;
if ("u" > typeof process) {
let runtimeName;
runtimeName = "u" > typeof Deno && "string" == typeof Deno.version?.deno ? "deno" : "u" > typeof Bun && "string" == typeof Bun.version ? "bun" : "node";
runtimeInfo = `${process.platform}-${process.arch} ${runtimeName}-${process.version}`;
runtimeProcessArgs = process.argv;
} else runtimeInfo = "u" < typeof navigator ? "unknown" : `${navigator.platform} ${navigator.userAgent}`;
var Command = class {
rawName;
description;
config;
cli;
options;
aliasNames;
name;
args;
commandAction;
usageText;
versionNumber;
examples;
helpCallback;
globalCommand;
constructor(rawName, description, config = {}, cli){
this.rawName = rawName;
this.description = description;
this.config = config;
this.cli = cli;
this.options = [];
this.aliasNames = [];
this.name = removeBrackets(rawName);
this.args = findAllBrackets(rawName);
this.examples = [];
}
usage(text) {
this.usageText = text;
return this;
}
allowUnknownOptions() {
this.config.allowUnknownOptions = true;
return this;
}
ignoreOptionDefaultValue() {
this.config.ignoreOptionDefaultValue = true;
return this;
}
version(version, customFlags = "-v, --version") {
this.versionNumber = version;
this.option(customFlags, "Display version number");
return this;
}
example(example) {
this.examples.push(example);
return this;
}
option(rawName, description, config) {
const option = new Option(rawName, description, config);
this.options.push(option);
return this;
}
alias(name) {
this.aliasNames.push(name);
return this;
}
action(callback) {
this.commandAction = callback;
return this;
}
isMatched(name) {
return this.name === name || this.aliasNames.includes(name);
}
get isDefaultCommand() {
return "" === this.name || this.aliasNames.includes("!");
}
get isGlobalCommand() {
return this instanceof GlobalCommand;
}
hasOption(name) {
name = name.split(".")[0];
return this.options.find((option)=>option.names.includes(name));
}
outputHelp() {
const { name, commands } = this.cli;
const { versionNumber, options: globalOptions, helpCallback } = this.cli.globalCommand;
let sections = [
{
body: `${name}${versionNumber ? `/${versionNumber}` : ""}`
}
];
sections.push({
title: "Usage",
body: ` $ ${name} ${this.usageText || this.rawName}`
});
if ((this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0) {
const longestCommandName = findLongest(commands.map((command)=>command.rawName));
sections.push({
title: "Commands",
body: commands.map((command)=>` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`).join("\n")
}, {
title: "For more info, run any command with the `--help` flag",
body: commands.map((command)=>` $ ${name}${"" === command.name ? "" : ` ${command.name}`} --help`).join("\n")
});
}
let options = this.isGlobalCommand ? globalOptions : [
...this.options,
...globalOptions || []
];
if (!this.isGlobalCommand && !this.isDefaultCommand) options = options.filter((option)=>"version" !== option.name);
if (options.length > 0) {
const longestOptionName = findLongest(options.map((option)=>option.rawName));
sections.push({
title: "Options",
body: options.map((option)=>` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${void 0 === option.config.default ? "" : `(default: ${option.config.default})`}`).join("\n")
});
}
if (this.examples.length > 0) sections.push({
title: "Examples",
body: this.examples.map((example)=>{
if ("function" == typeof example) return example(name);
return example;
}).join("\n")
});
if (helpCallback) sections = helpCallback(sections) || sections;
console.info(sections.map((section)=>section.title ? `${section.title}:\n${section.body}` : section.body).join("\n\n"));
}
outputVersion() {
const { name } = this.cli;
const { versionNumber } = this.cli.globalCommand;
if (versionNumber) console.info(`${name}/${versionNumber} ${runtimeInfo}`);
}
checkRequiredArgs() {
const minimalArgsCount = this.args.filter((arg)=>arg.required).length;
if (this.cli.args.length < minimalArgsCount) throw new CACError(`missing required args for command \`${this.rawName}\``);
}
checkUnknownOptions() {
const { options, globalCommand } = this.cli;
if (!this.config.allowUnknownOptions) {
for (const name of Object.keys(options))if ("--" !== name && !this.hasOption(name) && !globalCommand.hasOption(name)) throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
}
}
checkOptionValue() {
const { options: parsedOptions, globalCommand } = this.cli;
const options = [
...globalCommand.options,
...this.options
];
for (const option of options){
const value = parsedOptions[option.name.split(".")[0]];
if (option.required) {
const hasNegated = options.some((o)=>o.negated && o.names.includes(option.name));
if (true === value || false === value && !hasNegated) throw new CACError(`option \`${option.rawName}\` value is missing`);
}
}
}
checkUnusedArgs() {
const maximumArgsCount = this.args.some((arg)=>arg.variadic) ? 1 / 0 : this.args.length;
if (maximumArgsCount < this.cli.args.length) throw new CACError(`Unused args: ${this.cli.args.slice(maximumArgsCount).map((arg)=>`\`${arg}\``).join(", ")}`);
}
};
var GlobalCommand = class extends Command {
constructor(cli){
super("@@global@@", "", {}, cli);
}
};
var CAC = class extends EventTarget {
name;
commands;
globalCommand;
matchedCommand;
matchedCommandName;
rawArgs;
args;
options;
showHelpOnExit;
showVersionOnExit;
constructor(name = ""){
super();
this.name = name;
this.commands = [];
this.rawArgs = [];
this.args = [];
this.options = {};
this.globalCommand = new GlobalCommand(this);
this.globalCommand.usage("<command> [options]");
}
usage(text) {
this.globalCommand.usage(text);
return this;
}
command(rawName, description, config) {
const command = new Command(rawName, description || "", config, this);
command.globalCommand = this.globalCommand;
this.commands.push(command);
return command;
}
option(rawName, description, config) {
this.globalCommand.option(rawName, description, config);
return this;
}
help(callback) {
this.globalCommand.option("-h, --help", "Display this message");
this.globalCommand.helpCallback = callback;
this.showHelpOnExit = true;
return this;
}
version(version, customFlags = "-v, --version") {
this.globalCommand.version(version, customFlags);
this.showVersionOnExit = true;
return this;
}
example(example) {
this.globalCommand.example(example);
return this;
}
outputHelp() {
if (this.matchedCommand) this.matchedCommand.outputHelp();
else this.globalCommand.outputHelp();
}
outputVersion() {
this.globalCommand.outputVersion();
}
setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
this.args = args;
this.options = options;
if (matchedCommand) this.matchedCommand = matchedCommand;
if (matchedCommandName) this.matchedCommandName = matchedCommandName;
return this;
}
unsetMatchedCommand() {
this.matchedCommand = void 0;
this.matchedCommandName = void 0;
}
parse(argv, { run = true } = {}) {
if (!argv) {
if (!runtimeProcessArgs) throw new Error("No argv provided and runtime process argv is not available.");
argv = runtimeProcessArgs;
}
this.rawArgs = argv;
if (!this.name) this.name = argv[1] ? getFileName(argv[1]) : "cli";
let shouldParse = true;
for (const command of this.commands){
const parsed = this.mri(argv.slice(2), command);
const commandName = parsed.args[0];
if (command.isMatched(commandName)) {
shouldParse = false;
const parsedInfo = {
...parsed,
args: parsed.args.slice(1)
};
this.setParsedInfo(parsedInfo, command, commandName);
this.dispatchEvent(new CustomEvent(`command:${commandName}`, {
detail: command
}));
}
}
if (shouldParse) {
for (const command of this.commands)if (command.isDefaultCommand) {
shouldParse = false;
const parsed = this.mri(argv.slice(2), command);
this.setParsedInfo(parsed, command);
this.dispatchEvent(new CustomEvent("command:!", {
detail: command
}));
}
}
if (shouldParse) {
const parsed = this.mri(argv.slice(2));
this.setParsedInfo(parsed);
}
if (this.options.help && this.showHelpOnExit) {
this.outputHelp();
run = false;
this.unsetMatchedCommand();
}
if (this.options.version && this.showVersionOnExit && null == this.matchedCommandName) {
this.outputVersion();
run = false;
this.unsetMatchedCommand();
}
const parsedArgv = {
args: this.args,
options: this.options
};
if (run) this.runMatchedCommand();
if (!this.matchedCommand && this.args[0]) this.dispatchEvent(new CustomEvent("command:*", {
detail: this.args[0]
}));
return parsedArgv;
}
mri(argv, command) {
const cliOptions = [
...this.globalCommand.options,
...command ? command.options : []
];
const mriOptions = getMriOptions(cliOptions);
let argsAfterDoubleDashes = [];
const doubleDashesIndex = argv.indexOf("--");
if (-1 !== doubleDashesIndex) {
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
argv = argv.slice(0, doubleDashesIndex);
}
let parsed = lib_default(argv, mriOptions);
parsed = Object.keys(parsed).reduce((res, name)=>({
...res,
[camelcaseOptionName(name)]: parsed[name]
}), {
_: []
});
const args = parsed._;
const options = {
"--": argsAfterDoubleDashes
};
const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
const transforms = Object.create(null);
for (const cliOption of cliOptions){
if (!ignoreDefault && void 0 !== cliOption.config.default) for (const name of cliOption.names)options[name] = cliOption.config.default;
if (Array.isArray(cliOption.config.type) && void 0 === transforms[cliOption.name]) {
transforms[cliOption.name] = Object.create(null);
transforms[cliOption.name].shouldTransform = true;
transforms[cliOption.name].transformFunction = cliOption.config.type[0];
}
}
for (const key of Object.keys(parsed))if ("_" !== key) {
setDotProp(options, key.split("."), parsed[key]);
setByType(options, transforms);
}
return {
args,
options
};
}
runMatchedCommand() {
const { args, options, matchedCommand: command } = this;
if (!command || !command.commandAction) return;
command.checkUnknownOptions();
command.checkOptionValue();
command.checkRequiredArgs();
command.checkUnusedArgs();
const actionArgs = [];
command.args.forEach((arg, index)=>{
if (arg.variadic) actionArgs.push(args.slice(index));
else actionArgs.push(args[index]);
});
actionArgs.push(options);
return command.commandAction.apply(this, actionArgs);
}
};
const cac = (name = "")=>new CAC(name);
const commonOptions = (command)=>command.option('-c, --config <path>', 'config file').option('--config-name <name>', 'Name(s) of the configuration to use.', {
type: [
String
],
default: []
}).option('--config-loader <loader>', 'Specify the loader to load the config file, can be `auto`, `jiti` or `native`.', {
default: 'auto'
}).option('--env <env>', 'env passed to config function', {
type: [
String
],
default: []
}).option('--node-env <value>', 'sets `process.env.NODE_ENV` to be specified value');
function normalizeDevtoolOption(value) {
if ('string' == typeof value) {
const trimmed = value.trim();
if ('' === trimmed || 'false' === trimmed) return false;
if ('true' === trimmed) return 'source-map';
return trimmed;
}
if ('boolean' == typeof value) return value ? 'source-map' : false;
}
const normalizeCommonOptions = (options, action)=>{
const isEmptyArray = (arr)=>Array.isArray(arr) && 0 === arr.length;
for (const key of [
'entry',
'configName'
]){
const val = options[key];
if (isEmptyArray(val)) options[key] = void 0;
}
const env = Array.isArray(options.env) ? normalizeEnvToObject(options) : {};
options.env = env;
if ('serve' === action) setBuiltinEnvArg(env, 'SERVE', true);
else if ('build' === action) if (options.watch) setBuiltinEnvArg(env, 'WATCH', true);
else {
setBuiltinEnvArg(env, 'BUNDLE', true);
setBuiltinEnvArg(env, 'BUILD', true);
}
if ('devtool' in options) options.devtool = normalizeDevtoolOption(options.devtool);
};
const commonOptionsForBuildAndServe = (command)=>command.option('-d, --devtool <value>', 'set source map style for debugging. Use `false` to disable source maps.').option('--entry <entry>', 'entry file', {
type: [
String
],
default: []
}).option('-m, --mode <mode>', 'mode').option('-o, --output-path <dir>', 'output path dir').option('-w, --watch', 'watch');
function setBuiltinEnvArg(env, envNameSuffix, value) {
const envName = `RSPACK_${envNameSuffix}`;
if (!Object.prototype.hasOwnProperty.call(env, envName)) env[envName] = value;
}
const DANGEROUS_ENV_KEYS = new Set([
'__proto__',
'constructor',
'prototype'
]);
function normalizeEnvToObject(options) {
function parseValue(previous, value) {
const [allKeys, val] = value.split(/=(.+)/, 2);
const splitKeys = allKeys.split(/\.(?!$)/);
if (splitKeys.some((k)=>DANGEROUS_ENV_KEYS.has(k.replace(/=$/, '')))) return previous;
let prevRef = previous;
splitKeys.forEach((key, index)=>{
let someKey = key;
if (someKey.endsWith('=')) {
someKey = someKey.slice(0, -1);
prevRef[someKey] = void 0;
return;
}
if (!prevRef[someKey] || 'string' == typeof prevRef[someKey]) prevRef[someKey] = {};
if (index === splitKeys.length - 1) if ('string' == typeof val) prevRef[someKey] = val;
else prevRef[someKey] = true;
prevRef = prevRef[someKey];
});
return previous;
}
return (options.env ?? []).reduce(parseValue, {});
}
function setDefaultNodeEnv(options, defaultEnv) {
if (void 0 === process.env.NODE_ENV) process.env.NODE_ENV = 'string' == typeof options.nodeEnv ? options.nodeEnv : defaultEnv;
}
async function runBuild(cli, options) {
setDefaultNodeEnv(options, 'production');
normalizeCommonOptions(options, 'build');
const logger = cli.getLogger();
let createJsonStringifyStream;
if (options.json) {
const stream = await import("node:stream");
const jsonExt = await import("./json-ext.js");
createJsonStringifyStream = (value)=>stream.Readable.from(jsonExt.stringifyChunked(value));
}
const errorHandler = (error, stats)=>{
if (error) {
logger.error(error);
process.exit(2);
}
if (stats?.hasErrors()) process.exitCode = 1;
if (!compiler || !stats) return;
const getStatsOptions = ()=>{
if (cli.isMultipleCompiler(compiler)) return {
children: compiler.compilers.map((item)=>item.options ? item.options.stats : void 0)
};
return compiler.options?.stats;
};
const statsOptions = getStatsOptions();
if (options.json && createJsonStringifyStream) {
const handleWriteError = (error)=>{
logger.error(error);
process.exit(2);
};
if (true === options.json) createJsonStringifyStream(stats.toJson(statsOptions)).on('error', handleWriteError).pipe(process.stdout).on('error', handleWriteError).on('close', ()=>process.stdout.write('\n'));
else if ('string' == typeof options.json) createJsonStringifyStream(stats.toJson(statsOptions)).on('error', handleWriteError).pipe(node_fs.createWriteStream(options.json)).on('error', handleWriteError).on('close', ()=>{
process.stderr.write(`[rspack-cli] ${cli.colors.green(`stats are successfully stored as json to ${options.json}`)}\n`);
});
} else {
const printedStats = stats.toString(statsOptions);
if (printedStats) logger.raw(printedStats);
}
};
const userOption = await cli.buildCompilerConfig(options, 'build');
const compiler = await cli.createCompiler(userOption, errorHandler);
if (!compiler || cli.isWatch(compiler)) return;
compiler.run((error, stats)=>{
compiler.close((closeErr)=>{
if (closeErr) logger.error(closeErr);
errorHandler(error, stats);
});
});
}
class BuildCommand {
apply(cli) {
const command = cli.program.command('', 'run the Rspack build').alias('build').alias('bundle').alias('b');
commonOptionsForBuildAndServe(commonOptions(command)).option('--json [path]', 'emit stats json');
command.action(cli.wrapAction(async (options)=>{
await runBuild(cli, options);
}));
}
}
class PreviewCommand {
apply(cli) {
const command = cli.program.command('preview [dir]', 'run the Rspack server for build output').alias('p');
commonOptions(command).option('--public-path <path>', 'static resource server path').option('--port <port>', 'preview server port').option('--host <host>', 'preview server host').option('--open', 'open browser').option('--server <config>', 'Configuration items for the server.');
command.action(cli.wrapAction(async (dir, options)=>{
setDefaultNodeEnv(options, 'production');
normalizeCommonOptions(options, 'preview');
let RspackDevServer;
try {
const devServerModule = await import("@rspack/dev-server");
RspackDevServer = devServerModule.RspackDevServer;
} catch (error) {
const logger = cli.getLogger();
if (error?.code === 'MODULE_NOT_FOUND' || error?.code === 'ERR_MODULE_NOT_FOUND') logger.error('The "@rspack/dev-server" package is required to use the preview command.\nPlease install it by running:\n pnpm add -D @rspack/dev-server\n or\n npm install -D @rspack/dev-server');
else logger.error('Failed to load "@rspack/dev-server":\n' + (error?.message || String(error)));
process.exit(1);
}
let { config } = await cli.loadConfig(options);
config = await getPreviewConfig(config, options, dir);
if (!Array.isArray(config)) config = [
config
];
const singleConfig = config.find((item)=>item.devServer) || config[0];
const devServerOptions = singleConfig.devServer;
try {
const compiler = rspack({
entry: {}
});
if (!compiler) return;
const server = new RspackDevServer(devServerOptions, compiler);
await server.start();
} catch (error) {
const logger = cli.getLogger();
logger.error(error);
process.exit(2);
}
}));
}
}
async function getPreviewConfig(item, options, dir) {
const DEFAULT_ROOT = 'dist';
const internalPreviewConfig = (item)=>{
const devServer = false === item.devServer ? void 0 : item.devServer;
item.devServer = {
static: {
directory: dir ? node_path.join(item.context ?? process.cwd(), dir) : item.output?.path ?? node_path.join(item.context ?? process.cwd(), DEFAULT_ROOT),
publicPath: options.publicPath ?? '/'
},
hot: false,
port: options.port ?? devServer?.port ?? 8080,
proxy: devServer?.proxy,
host: options.host ?? devServer?.host,
open: options.open ?? devServer?.open,
server: options.server ?? devServer?.server,
historyApiFallback: devServer?.historyApiFallback
};
return item;
};
if (Array.isArray(item)) return Promise.all(item.map(internalPreviewConfig));
return internalPreviewConfig(item);
}
function normalizeHotOption(value) {
if ('false' === value) return false;
if ('true' === value) return true;
return value;
}
class ServeCommand {
apply(cli) {
const command = cli.program.command('serve', 'run the rspack dev server.').alias('server').alias('s').alias('dev');
commonOptionsForBuildAndServe(commonOptions(command)).option('--hot [mode]', 'enables hot module replacement').option('--port <port>', 'allows to specify a port to use').option('--host <host>', 'allows to specify a hostname to use').option('--open [value]', 'open browser on server start; pass --no-open to disable, or --open <url> to open a specific URL');
command.action(cli.wrapAction(async (cliOptions)=>{
setDefaultNodeEnv(cliOptions, 'development');
normalizeCommonOptions(cliOptions, 'serve');
cliOptions.hot = normalizeHotOption(cliOptions.hot);
let RspackDevServer;
try {
const devServerModule = await import("@rspack/dev-server");
RspackDevServer = devServerModule.RspackDevServer;
} catch (error) {
const logger = cli.getLogger();
if (error?.code === 'MODULE_NOT_FOUND' || error?.code === 'ERR_MODULE_NOT_FOUND') logger.error('The "@rspack/dev-server" package is required to use the serve command.\nPlease install it by running:\n pnpm add -D @rspack/dev-server\n or\n npm install -D @rspack/dev-server');
else logger.error('Failed to load "@rspack/dev-server":\n' + (error?.message || String(error)));
process.exit(1);
}
const userConfig = await cli.buildCompilerConfig(cliOptions, 'serve');
const compiler = await cli.createCompiler(userConfig);
if (!compiler) return;
const isMultiCompiler = cli.isMultipleCompiler(compiler);
const compilers = isMultiCompiler ? compiler.compilers : [
compiler
];
const userConfigs = isMultiCompiler ? userConfig : [
userConfig
];
const possibleCompilers = compilers.filter((compiler)=>compiler.options.devServer);
const usedPorts = [];
const servers = [];
const compilerForDevServer = possibleCompilers.length > 0 ? possibleCompilers[0] : compilers[0];
for (const [index, compiler] of compilers.entries()){
const userConfig = userConfigs[index];
const existingDevServer = compiler.options.devServer;
if (false === existingDevServer) continue;
const devServer = existingDevServer ?? (compiler.options.devServer = {});
const isWebAppOnly = compiler.platform.web && !compiler.platform.node && !compiler.platform.nwjs && !compiler.platform.electron && !compiler.platform.webworker;
if (isWebAppOnly && void 0 === userConfig.lazyCompilation) compiler.options.lazyCompilation = {
imports: true,
entries: false
};
devServer.hot = cliOptions.hot ?? devServer.hot ?? true;
if (false !== devServer.client) {
if (true === devServer.client || null == devServer.client) devServer.client = {};
devServer.client = {
overlay: {
errors: true,
warnings: false
},
...devServer.client
};
}
}
const compilerForDevServerOptions = compilerForDevServer.options.devServer;
const devServerOptions = false === compilerForDevServerOptions ? {} : compilerForDevServerOptions ?? (compilerForDevServer.options.devServer = {});
const { setupMiddlewares } = devServerOptions;
const lazyCompileMiddleware = rspack.lazyCompilationMiddleware(compiler);
devServerOptions.setupMiddlewares = (middlewares, server)=>{
let finalMiddlewares = middlewares;
if (setupMiddlewares) finalMiddlewares = setupMiddlewares(finalMiddlewares, server);
return [
...finalMiddlewares,
lazyCompileMiddleware
];
};
devServerOptions.hot = cliOptions.hot ?? devServerOptions.hot ?? true;
devServerOptions.host = cliOptions.host || devServerOptions.host;
devServerOptions.port = cliOptions.port ?? devServerOptions.port;
if (void 0 !== cliOptions.open) devServerOptions.open = cliOptions.open;
if (false !== devServerOptions.client) {
if (true === devServerOptions.client || null == devServerOptions.client) devServerOptions.client = {};
devServerOptions.client = {
overlay: {
errors: true,
warnings: false
},
...devServerOptions.client
};
}
if (devServerOptions.port) {
const portNumber = Number(devServerOptions.port);
if (!Number.isNaN(portNumber)) {
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);
}
}));
}
}
function mergeWith(objects, customizer) {
const [first, ...rest] = objects;
let ret = first;
rest.forEach((a)=>{
ret = mergeTo(ret, a, customizer);
});
return ret;
}
function mergeTo(a, b, customizer) {
const ret = {};
Object.keys(a).concat(Object.keys(b)).forEach((k)=>{
const v = customizer(a[k], b[k], k);
ret[k] = void 0 === v ? a[k] : v;
});
return ret;
}
const merge_with = mergeWith;
function isRegex(o) {
return o instanceof RegExp;
}
function isPlainObject(value) {
if ('[object Object]' !== Object.prototype.toString.call(value)) return false;
const proto = Object.getPrototypeOf(value);
if (null === proto) return true;
let baseProto = proto;
while(null !== Object.getPrototypeOf(baseProto))baseProto = Object.getPrototypeOf(baseProto);
return proto === baseProto;
}
function isUndefined(value) {
return void 0 === value;
}
function isPromiseLike(value) {
return null !== value && ('object' == typeof value || 'function' == typeof value) && 'function' == typeof value.then;
}
const isArray = Array.isArray;
function joinArrays({ customizeArray, customizeObject, key } = {}) {
return function _joinArrays(a, b, k) {
const newKey = key ? `${key}.${k}` : k;
if ('function' == typeof a && 'function' == typeof b) return (...args)=>_joinArrays(a(...args), b(...args), k);
if (isArray(a) && isArray(b)) {
const customResult = customizeArray && customizeArray(a, b, newKey);
return customResult || [
...a,
...b
];
}
if (isRegex(b)) return b;
if (isPlainObject(a) && isPlainObject(b)) {
const customResult = customizeObject && customizeObject(a, b, newKey);
return customResult || merge_with([
a,
b
], joinArrays({
customizeArray,
customizeObject,
key: newKey
}));
}
if (isPlainObject(b)) return merge_with([
{},
b
], joinArrays({
customizeArray,
customizeObject,
key: newKey
}));
if (isArray(b)) return [
...b
];
return b;
};
}
function merge(firstConfiguration, ...configurations) {
return mergeWithCustomize({})(firstConfiguration, ...configurations);
}
function mergeWithCustomize(options) {
return function(firstConfiguration, ...configurations) {
if (isUndefined(firstConfiguration) || configurations.some(isUndefined)) throw new TypeError('Merging undefined is not supported');
if (isPromiseLike(firstConfiguration)) throw new TypeError('Promises are not supported');
if (!firstConfiguration) return {};
if (0 === configurations.length) {
if (Array.isArray(firstConfiguration)) {
if (0 === firstConfiguration.length) return {};
if (firstConfiguration.some(isUndefined)) throw new TypeError('Merging undefined is not supported');
if (isPromiseLike(firstConfiguration[0])) throw new TypeError('Promises are not supported');
return merge_with(firstConfiguration, joinArrays(options));
}
return firstConfiguration;
}
return merge_with([
firstConfiguration
].concat(configurations), joinArrays(options));
};
}
const DEFAULT_EXTENSIONS = [
'.ts',
'.js',
'.mts',
'.mjs',
'.cts',
'.cjs'
];
const findConfig = (basePath)=>DEFAULT_EXTENSIONS.map((ext)=>basePath + ext).find(node_fs.existsSync);
const utils_findConfig = findConfig;
const loadConfig_require = createRequire(import.meta.url);
const loadConfig_DEFAULT_CONFIG_NAME = 'rspack.config';
const JS_CONFIG_EXTENSION_REGEXP = /\.(?:js|mjs|cjs)$/;
const CONFIG_LOADER_VALUES = [
'auto',
'jiti',
'native'
];
const PREBUNDLED_JITI_PATH = new URL('../compiled/jiti/index.js', import.meta.url).href;
const supportsNativeTypeScript = ()=>{
const features = process.features;
return Boolean(features.typescript || process.versions.bun || process.versions.deno);
};
const normalizeConfigLoader = (configLoader)=>{
const normalizedLoader = configLoader ?? 'auto';
if (CONFIG_LOADER_VALUES.includes(normalizedLoader)) return normalizedLoader;
throw new Error(`config loader "${normalizedLoader}" is not supported. Expected one of: ${CONFIG_LOADER_VALUES.join(', ')}.`);
};
const resolveDefaultExport = (result)=>result && 'object' == typeof result && 'default' in result ? result.default : result;
const loadConfigWithNativeLoader = async (configPath)=>{
const configFileURL = pathToFileURL(configPath).href;
const loadedModule = await import(`${configFileURL}?t=${Date.now()}`);
return resolveDefaultExport(loadedModule);
};
let jitiInstancePromise;
const getJiti = async ()=>{
if (!jitiInstancePromise) jitiInstancePromise = import(PREBUNDLED_JITI_PATH).then((module)=>{
const createJiti = 'createJiti' in module ? module.createJiti : module.default;
return createJiti(import.meta.filename, {
moduleCache: false,
interopDefault: true,
nativeModules: [
"typescript"
]
});
});
return jitiInstancePromise;
};
const loadConfigWithJiti = async (configPath)=>{
const jiti = await getJiti();
return jiti.import(configPath, {
default: true
});
};
const loadConfigByPath = async (configPath, options)=>{
const configLoader = normalizeConfigLoader(options.configLoader);
const useNative = Boolean('native' === configLoader || 'auto' === configLoader && supportsNativeTypeScript());
if (useNative || JS_CONFIG_EXTENSION_REGEXP.test(configPath)) try {
return await loadConfigWithNativeLoader(configPath);
} catch (error) {
if ('native' === configLoader) throw error;
}
return loadConfigWithJiti(configPath);
};
const isConfigObject = (value)=>Boolean(value) && 'object' == typeof value && !Array.isArray(value);
const isRspackConfig = (value)=>Array.isArray(value) || isConfigObject(value);
const resolveRspackConfigExport = async (configExport, options)=>{
let loadedConfig = configExport;
if ('function' == typeof loadedConfig) {
let functionResult = loadedConfig(options.env, options);
if ('function' == typeof functionResult.then) functionResult = await functionResult;
if (void 0 === functionResult) throw new Error('[rspack-cli:loadConfig] The config function must return a config object.');
loadedConfig = functionResult;
}
if (!isRspackConfig(loadedConfig)) throw new Error(`[rspack-cli:loadConfig] The config must be an object, an array, or a function that returns one, get ${String(loadedConfig)}`);
return loadedConfig;
};
const checkIsMultiRspackOptions = (config)=>Array.isArray(config);
async function loadExtendedConfig(config, configPath, cwd, options, visitedPaths) {
const currentVisitedPaths = visitedPaths ?? new Set();
if (checkIsMultiRspackOptions(config)) {
const resultPathMap = new WeakMap();
const extendedConfigs = await Promise.all(config.map(async (item)=>{
const itemVisitedPaths = new Set(currentVisitedPaths);
const { config, pathMap } = await loadExtendedConfig(item, configPath, cwd, options, itemVisitedPaths);
resultPathMap.set(config, pathMap.get(config));
return config;
}));
extendedConfigs.parallelism = config.parallelism;
return {
config: extendedConfigs,
pathMap: resultPathMap
};
}
if (currentVisitedPaths.has(configPath)) throw new Error(`Recursive configuration detected. Config file "${configPath}" extends itself.`);
currentVisitedPaths.add(configPath);
const pathMap = new WeakMap();
pathMap.set(config, [
configPath
]);
if (!('extends' in config) || !config.extends) return {
config,
pathMap
};
const extendsList = Array.isArray(config.extends) ? config.extends : [
config.extends
];
const { extends: _, ...configWithoutExtends } = config;
const baseDir = node_path.dirname(configPath);
let resultConfig = configWithoutExtends;
pathMap.set(resultConfig, [
configPath
]);
for (const extendPath of extendsList){
let resolvedPath;
if (extendPath.startsWith('file://')) try {
resolvedPath = fileURLToPath(extendPath);
} catch {
throw new Error(`Invalid file URL '${extendPath}' in extends configuration.`);
}
else if (extendPath.startsWith('.') || extendPath.startsWith('/') || extendPath.includes(':\\')) {
resolvedPath = node_path.resolve(baseDir, extendPath);
if (!node_path.extname(resolvedPath)) {
const foundConfig = utils_findConfig(resolvedPath);
if (foundConfig) resolvedPath = foundConfig;
else throw new Error(`Extended configuration file "${resolvedPath}" not found.`);
}
} else try {
resolvedPath = loadConfig_require.resolve(extendPath, {
paths: [
baseDir,
cwd
]
});
} catch {
throw new Error(`Cannot find module '${extendPath}' to extend from.`);
}
if (!node_fs.existsSync(resolvedPath)) throw new Error(`Extended configuration file "${resolvedPath}" not found.`);
const loadedConfig = await loadConfigByPath(resolvedPath, options);
const resolvedConfig = await resolveRspackConfigExport(loadedConfig, options);
const { config: extendedConfig, pathMap: extendedPathMap } = await loadExtendedConfig(resolvedConfig, resolvedPath, cwd, options, currentVisitedPaths);
const configPaths = [
...pathMap.get(resultConfig) || [],
...extendedPathMap.get(extendedConfig) || []
];
resultConfig = merge(extendedConfig, resultConfig);
pathMap.set(resultConfig, configPaths);
}
return {
config: resultConfig,
pathMap
};
}
async function loadRspackConfig(options, cwd = process.cwd()) {
let configPath = '';
if (options.config) {
configPath = node_path.resolve(cwd, options.config);
if (!node_fs.existsSync(configPath)) throw new Error(`config file "${configPath}" not found.`);
} else {
const defaultConfig = utils_findConfig(node_path.resolve(cwd, loadConfig_DEFAULT_CONFIG_NAME));
if (!defaultConfig) return null;
configPath = defaultConfig;
}
const loadedConfig = await loadConfigByPath(configPath, options);
return {
loadedConfig,
configPath
};
}
function isEnvColorSupported() {
if ("u" < typeof process) return false;
const p = process;
const argv = p.argv ?? [];
const env = p.env ?? {};
return !('NO_COLOR' in env || argv.includes('--no-color')) && ('FORCE_COLOR' in env || argv.includes('--color') || 'win32' === p.platform || p.stdout?.isTTY && 'dumb' !== env.TERM || 'CI' in env);
}
function createAnsiFormatter(open, close, replace = open) {
const closeLength = close.length;
return (input)=>{
const string = String(input);
let index = string.indexOf(close, open.length);
if (-1 === index) return open + string + close;
let result = '';
let cursor = 0;
do {
result += string.substring(cursor, index) + replace;
cursor = index + closeLength;
index = string.indexOf(close, cursor);
}while (-1 !== index);
return open + result + string.substring(cursor) + close;
};
}
class RspackCLI {
colors;
program;
_actionPromise;
constructor(){
const program = cac('rspack');
this.colors = this.createColors();
this.program = program;
program.help();
program.version("2.0.5");
}
wrapAction(fn) {
return (...args)=>{
this._actionPromise = fn(...args);
return this._actionPromise;
};
}
async buildCompilerConfig(options, rspackCommand) {
const { config: rawConfig, pathMap } = await this.loadConfig(options);
const config = await this.buildConfig(rawConfig, pathMap, options, rspackCommand);
return config;
}
createCompiler(config, callback) {
const isWatch = Array.isArray(config) ? config.some((i)=>i.watch) : config.watch;
let compiler;
try {
compiler = rspack(config, isWatch ? callback : void 0);
if (!isWatch && compiler) compiler.unsafeFastDrop = true;
} catch (e) {
if (e instanceof rspack.ValidationError) {
this.getLogger().error(e.message);
process.exit(2);
} else if (e instanceof Error) {
if ('function' == typeof callback) callback(e);
else this.getLogger().error(e);
return null;
}
throw e;
}
return compiler;
}
createColors(useColor) {
const envSupported = isEnvColorSupported();
const enabled = useColor ?? envSupported;
if (!enabled) {
const passthrough = (text)=>String(text);
return {
isColorSupported: false,
red: passthrough,
yellow: passthrough,
cyan: passthrough,
green: passthrough
};
}
return {
isColorSupported: true,
red: createAnsiFormatter('\x1b[31m', '\x1b[39m'),
green: createAnsiFormatter('\x1b[32m', '\x1b[39m'),
yellow: createAnsiFormatter('\x1b[33m', '\x1b[39m'),
cyan: createAnsiFormatter('\x1b[36m', '\x1b[39m')
};
}
getLogger() {
return {
error: (val)=>console.error(`[rspack-cli] ${this.colors.red(node_util