Skip to content

Commit f274031

Browse files
feat: support more cli options for build command (#1258)
Co-authored-by: Timeless0911 <50201324+Timeless0911@users.noreply.github.com>
1 parent b0e5e09 commit f274031

File tree

37 files changed

+693
-68
lines changed

37 files changed

+693
-68
lines changed

packages/core/__mocks__/rslog.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ module.exports = {
22
logger: {
33
warn: () => {},
44
override: () => {},
5+
debug: () => {},
56
},
67
};

packages/core/src/cli/commands.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { LogLevel, RsbuildMode } from '@rsbuild/core';
22
import cac, { type CAC } from 'cac';
33
import type { ConfigLoader } from '../config';
4+
import type { Format, Syntax } from '../types/config';
45
import { logger } from '../utils/logger';
56
import { build } from './build';
6-
import { init } from './init';
7+
import { initConfig } from './initConfig';
78
import { inspect } from './inspect';
89
import { startMFDevServer } from './mf';
910
import { watchFilesForRestart } from './restart';
@@ -20,6 +21,19 @@ export type CommonOptions = {
2021

2122
export type BuildOptions = CommonOptions & {
2223
watch?: boolean;
24+
format?: Format;
25+
entry?: string[];
26+
distPath?: string;
27+
bundle?: boolean;
28+
syntax?: Syntax;
29+
target?: string;
30+
dts?: boolean;
31+
externals?: string[];
32+
minify?: boolean;
33+
clean?: boolean;
34+
autoExtension?: boolean;
35+
autoExternal?: boolean;
36+
tsconfig?: string;
2337
};
2438

2539
export type InspectOptions = CommonOptions & {
@@ -84,13 +98,45 @@ export function runCli(): void {
8498

8599
buildCommand
86100
.option('-w, --watch', 'turn on watch mode, watch for changes and rebuild')
101+
.option('--entry <entry>', 'set entry file or pattern (repeatable)', {
102+
type: [String],
103+
default: [],
104+
})
105+
.option('--dist-path <dir>', 'set output directory')
106+
.option('--bundle', 'enable bundle mode (use --no-bundle to disable)')
107+
.option(
108+
'--format <format>',
109+
'specify the output format (esm | cjs | umd | mf | iife)',
110+
)
111+
.option('--syntax <syntax>', 'set build syntax target (repeatable)')
112+
.option('--target <target>', 'set runtime target (web | node)')
113+
.option('--dts', 'emit declaration files (use --no-dts to disable)')
114+
.option('--externals <pkg>', 'add package to externals (repeatable)', {
115+
type: [String],
116+
default: [],
117+
})
118+
.option('--minify', 'minify output (use --no-minify to disable)')
119+
.option(
120+
'--clean',
121+
'clean output directory before build (use --no-clean to disable)',
122+
)
123+
.option(
124+
'--auto-extension',
125+
'control automatic extension redirect (use --no-auto-extension to disable)',
126+
)
127+
.option(
128+
'--auto-external',
129+
'control automatic dependency externalization (use --no-auto-external to disable)',
130+
)
131+
.option(
132+
'--tsconfig <path>',
133+
'use specific tsconfig (relative to project root)',
134+
)
87135
.action(async (options: BuildOptions) => {
88136
try {
89137
const cliBuild = async () => {
90-
const { config, watchFiles } = await init(options);
91-
138+
const { config, watchFiles } = await initConfig(options);
92139
await build(config, options);
93-
94140
if (options.watch) {
95141
watchFilesForRestart(watchFiles, async () => {
96142
await cliBuild();
@@ -120,7 +166,7 @@ export function runCli(): void {
120166
.action(async (options: InspectOptions) => {
121167
try {
122168
// TODO: inspect should output Rslib's config
123-
const { config } = await init(options);
169+
const { config } = await initConfig(options);
124170
await inspect(config, {
125171
lib: options.lib,
126172
mode: options.mode,
@@ -137,7 +183,7 @@ export function runCli(): void {
137183
mfDevCommand.action(async (options: CommonOptions) => {
138184
try {
139185
const cliMfDev = async () => {
140-
const { config, watchFiles } = await init(options);
186+
const { config, watchFiles } = await initConfig(options);
141187
await startMFDevServer(config, {
142188
lib: options.lib,
143189
});

packages/core/src/cli/init.ts

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import path from 'node:path';
2+
import util from 'node:util';
3+
import { loadEnv, type RsbuildEntry } from '@rsbuild/core';
4+
import { loadConfig } from '../config';
5+
import type { RsbuildConfigOutputTarget, RslibConfig } from '../types';
6+
import { getAbsolutePath } from '../utils/helper';
7+
import { logger } from '../utils/logger';
8+
import type { BuildOptions, CommonOptions } from './commands';
9+
import { onBeforeRestart } from './restart';
10+
11+
const getEnvDir = (cwd: string, envDir?: string) => {
12+
if (envDir) {
13+
return path.isAbsolute(envDir) ? envDir : path.resolve(cwd, envDir);
14+
}
15+
return cwd;
16+
};
17+
18+
export const parseEntryOption = (
19+
entries?: string[],
20+
): Record<string, string> | undefined => {
21+
if (!entries?.length) return undefined;
22+
23+
const entryList: { key: string; value: string; explicit: boolean }[] = [];
24+
25+
for (const rawEntry of entries) {
26+
const value = rawEntry?.trim();
27+
if (!value) continue;
28+
29+
const equalIndex = value.indexOf('=');
30+
if (equalIndex > -1) {
31+
const name = value.slice(0, equalIndex).trim();
32+
const entryPath = value.slice(equalIndex + 1).trim();
33+
if (name && entryPath) {
34+
entryList.push({ key: name, value: entryPath, explicit: true });
35+
continue;
36+
}
37+
}
38+
39+
const basename = path.basename(value, path.extname(value));
40+
entryList.push({ key: basename, value, explicit: false });
41+
}
42+
43+
const keyCount: Record<string, number> = {};
44+
for (const { key, explicit } of entryList) {
45+
if (!explicit) keyCount[key] = (keyCount[key] ?? 0) + 1;
46+
}
47+
48+
const keyIndex: Record<string, number> = {};
49+
const parsed: Record<string, string> = {};
50+
51+
for (const { key, value, explicit } of entryList) {
52+
const needsIndex = !explicit && (keyCount[key] ?? 0) > 1;
53+
const finalKey = needsIndex ? `${key}${keyIndex[key] ?? 0}` : key;
54+
if (needsIndex) keyIndex[key] = (keyIndex[key] ?? 0) + 1;
55+
parsed[finalKey] = value;
56+
}
57+
58+
return Object.keys(parsed).length ? parsed : undefined;
59+
};
60+
61+
export const applyCliOptions = (
62+
config: RslibConfig,
63+
options: BuildOptions,
64+
root: string,
65+
): void => {
66+
if (options.root) config.root = root;
67+
if (options.logLevel) config.logLevel = options.logLevel;
68+
69+
for (const lib of config.lib) {
70+
if (options.format !== undefined) lib.format = options.format;
71+
if (options.bundle !== undefined) lib.bundle = options.bundle;
72+
if (options.tsconfig !== undefined) {
73+
lib.source ||= {};
74+
lib.source.tsconfigPath = options.tsconfig;
75+
}
76+
const entry = parseEntryOption(options.entry);
77+
if (entry !== undefined) {
78+
lib.source ||= {};
79+
lib.source.entry = entry as RsbuildEntry;
80+
}
81+
const syntax = options.syntax;
82+
if (syntax !== undefined) lib.syntax = syntax;
83+
if (options.dts !== undefined) lib.dts = options.dts;
84+
if (options.autoExtension !== undefined)
85+
lib.autoExtension = options.autoExtension;
86+
if (options.autoExternal !== undefined)
87+
lib.autoExternal = options.autoExternal;
88+
const output = lib.output ?? {};
89+
if (options.target !== undefined)
90+
output.target = options.target as RsbuildConfigOutputTarget;
91+
if (options.minify !== undefined) output.minify = options.minify;
92+
if (options.clean !== undefined) output.cleanDistPath = options.clean;
93+
const externals = options.externals?.filter(Boolean) ?? [];
94+
if (externals.length > 0) output.externals = externals;
95+
if (options.distPath) {
96+
output.distPath = {
97+
...(typeof output.distPath === 'object' ? output.distPath : {}),
98+
root: options.distPath,
99+
};
100+
}
101+
}
102+
};
103+
104+
export async function initConfig(options: CommonOptions): Promise<{
105+
config: RslibConfig;
106+
configFilePath: string;
107+
watchFiles: string[];
108+
}> {
109+
const cwd = process.cwd();
110+
const root = options.root ? getAbsolutePath(cwd, options.root) : cwd;
111+
const envs = loadEnv({
112+
cwd: getEnvDir(root, options.envDir),
113+
mode: options.envMode,
114+
});
115+
116+
onBeforeRestart(envs.cleanup);
117+
118+
const { content: config, filePath: configFilePath } = await loadConfig({
119+
cwd: root,
120+
path: options.config,
121+
envMode: options.envMode,
122+
loader: options.configLoader,
123+
});
124+
125+
config.source ||= {};
126+
config.source.define = {
127+
...envs.publicVars,
128+
...config.source.define,
129+
};
130+
131+
applyCliOptions(config, options, root);
132+
133+
logger.debug('Rslib config used to generate Rsbuild environments:');
134+
logger.debug(`\n${util.inspect(config, { depth: null, colors: true })}`);
135+
136+
return {
137+
config,
138+
configFilePath,
139+
watchFiles: [configFilePath, ...envs.filePaths],
140+
};
141+
}

0 commit comments

Comments
 (0)