Skip to content

Commit 15a37c3

Browse files
committed
cr
1 parent e1cfaff commit 15a37c3

File tree

10 files changed

+118
-99
lines changed

10 files changed

+118
-99
lines changed

packages/core/src/cli/commands.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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 } from '../types/config';
4+
import type { Format, Syntax } from '../types/config';
55
import { logger } from '../utils/logger';
66
import { build } from './build';
77
import { initConfig } from './initConfig';
@@ -17,11 +17,15 @@ export type CommonOptions = {
1717
lib?: string[];
1818
configLoader?: ConfigLoader;
1919
logLevel?: LogLevel;
20+
};
21+
22+
export type BuildOptions = CommonOptions & {
23+
watch?: boolean;
2024
format?: Format;
2125
entry?: string[];
2226
distPath?: string;
2327
bundle?: boolean;
24-
syntax?: string;
28+
syntax?: Syntax;
2529
target?: string;
2630
dts?: boolean;
2731
externals?: string[];
@@ -32,10 +36,6 @@ export type CommonOptions = {
3236
tsconfig?: string;
3337
};
3438

35-
export type BuildOptions = CommonOptions & {
36-
watch?: boolean;
37-
};
38-
3939
export type InspectOptions = CommonOptions & {
4040
mode?: RsbuildMode;
4141
output?: string;

packages/core/src/cli/initConfig.ts

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ import path from 'node:path';
22
import util from 'node:util';
33
import { loadEnv, type RsbuildEntry } from '@rsbuild/core';
44
import { loadConfig } from '../config';
5-
import type {
6-
EcmaScriptVersion,
7-
RsbuildConfigOutputTarget,
8-
RslibConfig,
9-
Syntax,
10-
} from '../types';
5+
import type { RsbuildConfigOutputTarget, RslibConfig } from '../types';
116
import { getAbsolutePath } from '../utils/helper';
127
import { logger } from '../utils/logger';
13-
import type { CommonOptions } from './commands';
8+
import type { BuildOptions, CommonOptions } from './commands';
149
import { onBeforeRestart } from './restart';
1510

1611
const getEnvDir = (cwd: string, envDir?: string) => {
@@ -23,67 +18,50 @@ const getEnvDir = (cwd: string, envDir?: string) => {
2318
export const parseEntryOption = (
2419
entries?: string[],
2520
): Record<string, string> | undefined => {
26-
if (!entries || entries.length === 0) {
27-
return undefined;
28-
}
21+
if (!entries?.length) return undefined;
2922

30-
const parsed: Record<string, string> = {};
31-
let unnamedIndex = 0;
23+
const entryList: Array<{ key: string; value: string; explicit: boolean }> =
24+
[];
3225

3326
for (const rawEntry of entries) {
3427
const value = rawEntry?.trim();
35-
if (!value) {
36-
continue;
37-
}
28+
if (!value) continue;
3829

3930
const equalIndex = value.indexOf('=');
4031
if (equalIndex > -1) {
4132
const name = value.slice(0, equalIndex).trim();
4233
const entryPath = value.slice(equalIndex + 1).trim();
4334
if (name && entryPath) {
44-
parsed[name] = entryPath;
35+
entryList.push({ key: name, value: entryPath, explicit: true });
4536
continue;
4637
}
4738
}
4839

49-
unnamedIndex += 1;
50-
const key = unnamedIndex === 1 ? 'index' : `entry${unnamedIndex}`;
51-
parsed[key] = value;
40+
const basename = path.basename(value, path.extname(value));
41+
entryList.push({ key: basename, value, explicit: false });
5242
}
5343

54-
return Object.keys(parsed).length === 0 ? undefined : parsed;
55-
};
44+
const keyCount: Record<string, number> = {};
45+
for (const { key, explicit } of entryList) {
46+
if (!explicit) keyCount[key] = (keyCount[key] ?? 0) + 1;
47+
}
5648

57-
// export const parseSyntaxOption = (syntax?: string): Syntax | undefined => {
58-
// if (!syntax) {
59-
// return undefined;
60-
// }
61-
62-
// const trimmed = syntax.trim();
63-
// if (!trimmed) {
64-
// return undefined;
65-
// }
66-
67-
// if (trimmed.startsWith('[')) {
68-
// try {
69-
// const parsed = JSON.parse(trimmed);
70-
// if (Array.isArray(parsed)) {
71-
// return parsed;
72-
// }
73-
// } catch (e) {
74-
// const reason = e instanceof Error ? e.message : String(e);
75-
// throw new Error(
76-
// `Failed to parse --syntax option "${trimmed}" as JSON array: ${reason}`,
77-
// );
78-
// }
79-
// }
80-
81-
// return trimmed as EcmaScriptVersion;
82-
// };
49+
const keyIndex: Record<string, number> = {};
50+
const parsed: Record<string, string> = {};
51+
52+
for (const { key, value, explicit } of entryList) {
53+
const needsIndex = !explicit && keyCount[key] > 1;
54+
const finalKey = needsIndex ? `${key}${keyIndex[key] ?? 0}` : key;
55+
if (needsIndex) keyIndex[key] = (keyIndex[key] ?? 0) + 1;
56+
parsed[finalKey] = value;
57+
}
58+
59+
return Object.keys(parsed).length ? parsed : undefined;
60+
};
8361

8462
export const applyCliOptions = (
8563
config: RslibConfig,
86-
options: CommonOptions,
64+
options: BuildOptions,
8765
root: string,
8866
): void => {
8967
if (options.root) config.root = root;
@@ -101,7 +79,7 @@ export const applyCliOptions = (
10179
lib.source ||= {};
10280
lib.source.entry = entry as RsbuildEntry;
10381
}
104-
const syntax = options.syntax as Syntax | undefined;
82+
const syntax = options.syntax;
10583
if (syntax !== undefined) lib.syntax = syntax;
10684
if (options.dts !== undefined) lib.dts = options.dts;
10785
if (options.autoExtension !== undefined)

packages/core/tests/cli.test.ts

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import { describe, expect, test } from '@rstest/core';
22
import type { CommonOptions } from '../src/cli/commands';
3-
import {
4-
applyCliOptions,
5-
parseEntryOption,
6-
parseSyntaxOption,
7-
} from '../src/cli/initConfig';
3+
import { applyCliOptions, parseEntryOption } from '../src/cli/initConfig';
84
import type { RslibConfig } from '../src/types';
95

106
describe('parseEntryOption', () => {
@@ -14,46 +10,58 @@ describe('parseEntryOption', () => {
1410
expect(parseEntryOption(['', ' '])).toBeUndefined();
1511
});
1612

17-
test('parses named and positional entries with trimming', () => {
13+
test('parses named entries with explicit keys', () => {
1814
const result = parseEntryOption([
1915
' main = ./src/main.ts ',
20-
' ./src/utils.ts ',
2116
'entry=./src/entry.ts',
22-
'./src/extra.ts',
2317
]);
2418

2519
expect(result).toEqual({
2620
main: './src/main.ts',
27-
index: './src/utils.ts',
2821
entry: './src/entry.ts',
29-
entry2: './src/extra.ts',
3022
});
3123
});
32-
});
3324

34-
describe('parseSyntaxOption', () => {
35-
test('returns undefined for missing or whitespace values', () => {
36-
expect(parseSyntaxOption()).toBeUndefined();
37-
expect(parseSyntaxOption('')).toBeUndefined();
38-
expect(parseSyntaxOption(' ')).toBeUndefined();
39-
});
25+
test('uses basename as key and handles conflicts with index suffix', () => {
26+
const result = parseEntryOption([
27+
'./src/utils.ts', // unique basename
28+
'./src/extra.js', // unique basename
29+
'./components/Button.tsx', // unique basename
30+
'./src/index.ts', // conflicts with other index files
31+
'./pages/index.tsx', // conflicts with other index files
32+
'./lib/index.js', // conflicts with other index files
33+
'./src/home.ts', // conflicts with other home file
34+
'custom=./pages/custom.tsx', // explicit key (not affected by conflicts)
35+
'./lib/home.js', // conflicts with other home file
36+
]);
4037

41-
test('returns the trimmed ECMAScript version when not a JSON array', () => {
42-
expect(parseSyntaxOption(' es2020 ')).toBe('es2020');
38+
expect(result).toEqual({
39+
utils: './src/utils.ts',
40+
extra: './src/extra.js',
41+
Button: './components/Button.tsx',
42+
index0: './src/index.ts',
43+
index1: './pages/index.tsx',
44+
index2: './lib/index.js',
45+
home0: './src/home.ts',
46+
custom: './pages/custom.tsx',
47+
home1: './lib/home.js',
48+
});
4349
});
4450

45-
test('parses JSON array syntax', () => {
46-
expect(parseSyntaxOption('["chrome 120", "firefox 115"]')).toEqual([
47-
'chrome 120',
48-
'firefox 115',
51+
test('mixed named and unnamed entries with trimming', () => {
52+
const result = parseEntryOption([
53+
' main = ./src/main.ts ',
54+
' ./src/utils.ts ',
55+
'entry=./src/entry.ts',
56+
'./src/extra.ts',
4957
]);
50-
});
5158

52-
test('throws descriptive error when JSON parsing fails', () => {
53-
const parseInvalidSyntax = () => parseSyntaxOption('[invalid');
54-
expect(parseInvalidSyntax).toThrowError(
55-
/Failed to parse --syntax option "\[inv.*JSON array/,
56-
);
59+
expect(result).toEqual({
60+
main: './src/main.ts',
61+
utils: './src/utils.ts',
62+
entry: './src/entry.ts',
63+
extra: './src/extra.ts',
64+
});
5765
});
5866
});
5967

@@ -98,7 +106,7 @@ describe('applyCliOptions', () => {
98106
bundle: false,
99107
tsconfig: './tsconfig.build.json',
100108
entry: [' main=src/main.ts ', ' src/utils.ts ', 'src/extra.ts'],
101-
syntax: '["node 18"]',
109+
syntax: ['node 18'],
102110
dts: true,
103111
autoExtension: false,
104112
autoExternal: false,
@@ -127,8 +135,8 @@ describe('applyCliOptions', () => {
127135
expect(lib.source?.tsconfigPath).toBe('./tsconfig.build.json');
128136
expect(lib.source?.entry).toEqual({
129137
main: 'src/main.ts',
130-
index: 'src/utils.ts',
131-
entry2: 'src/extra.ts',
138+
utils: 'src/utils.ts',
139+
extra: 'src/extra.ts',
132140
});
133141

134142
expect(lib.output).toBe(outputBefore);

packages/core/tests/config.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { join } from 'node:path';
22
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
33
import { inspect } from '@rslib/core';
44
import { describe, expect, rs, test } from '@rstest/core';
5+
import type { BuildOptions } from '../src/cli/commands';
56
import { initConfig } from '../src/cli/initConfig';
67
import {
78
composeCreateRsbuildConfig,
@@ -155,7 +156,7 @@ describe('CLI options', () => {
155156
const fixtureDir = join(__dirname, 'fixtures/config/cli-options');
156157
const configFilePath = join(fixtureDir, 'rslib.config.ts');
157158

158-
const { config } = await initConfig({
159+
const options: BuildOptions = {
159160
config: configFilePath,
160161
entry: ['index=src/main.ts', 'utils=src/utils.ts'],
161162
distPath: 'build',
@@ -169,8 +170,9 @@ describe('CLI options', () => {
169170
autoExtension: false,
170171
autoExternal: false,
171172
tsconfig: 'tsconfig.build.json',
172-
});
173+
};
173174

175+
const { config } = await initConfig(options);
174176
expect(config).toMatchInlineSnapshot(`
175177
{
176178
"_privateMeta": {

tests/integration/cli/build/build.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os from 'node:os';
21
import path from 'node:path';
32
import { stripVTControlCharacters as stripAnsi } from 'node:util';
43
import { describe, expect, test } from '@rstest/core';
@@ -186,7 +185,7 @@ describe('build command', async () => {
186185
lib: [
187186
{
188187
bundle: false,
189-
source: { entry: { index: './src/*' } },
188+
source: { entry: { '*': './src/*' } },
190189
format: 'esm',
191190
output: {
192191
distPath: { root: 'dist/ok' },

tests/integration/preserve-jsx/index.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { join } from 'node:path';
2+
import { stripVTControlCharacters as stripAnsi } from 'node:util';
23
import { expect, test } from '@rstest/core';
34
import { buildAndGetResults, proxyConsole, queryContent } from 'test-helper';
45

@@ -48,7 +49,7 @@ test('throw error when preserve JSX with bundle mode', async () => {
4849
try {
4950
await buildAndGetResults({ fixturePath });
5051
} catch {
51-
expect(logs).toMatchInlineSnapshot(`
52+
expect(logs.map((l) => stripAnsi(l))).toMatchInlineSnapshot(`
5253
[
5354
"error Bundle mode does not support preserving JSX syntax. Set "bundle" to "false" or change the JSX runtime to \`automatic\` or \`classic\`. Check out https://rslib.rs/guide/solution/react#jsx-transform for more details.",
5455
]

website/docs/en/config/rsbuild/source.mdx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ Replaces variables in your code with other values or expressions at compile time
2121

2222
## source.entry <RsbuildDocBadge path="/config/source/entry" text="source.entry" />
2323

24-
- **CLI:** `--entry <path>` (repeatable, e.g. `--entry index=src/index.ts` or `--entry src/index.ts`)
24+
- **CLI:** `--entry <path>`. Can be repeated, e.g., `--entry index=src/index.ts` or `--entry src/index.ts`.
25+
26+
The CLI argument supports two formats:
27+
1. `key=value`: Explicitly specify the entry name, e.g., `--entry main=src/main.ts`
28+
2. `value`: Only specify the path, in which case the basename of the file (without extension) will be automatically used as the entry name. For example, `--entry src/index.ts` will generate an entry named `index`. If there are name conflicts, numeric suffixes (starting from 0) will be added to all conflicting entries, e.g., `index0`, `index1`.
2529

2630
Used to set the entry modules for building.
2731

@@ -44,7 +48,6 @@ const defaultEntry = {
4448
};
4549
```
4650

47-
When no name is provided, the CLI assigns placeholders such as `index`, `entry2`, etc. automatically.
4851
:::info
4952
Check out the [lib.bundle](/config/lib/bundle#set-entry) to learn more about how to set entry for bundle and bundleless project.
5053
:::

website/docs/en/guide/basic/cli.mdx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,20 @@ Usage:
4646
$ rslib build
4747

4848
Options:
49-
-w --watch turn on watch mode, watch for changes and rebuild
49+
-w, --watch turn on watch mode, watch for changes and rebuild
50+
--entry <entry> set entry file or pattern (repeatable)
51+
--dist-path <dir> set output directory
52+
--bundle enable bundle mode (use --no-bundle to disable)
53+
--format <format> specify the output format (esm | cjs | umd | mf | iife)
54+
--syntax <syntax> set build syntax target (repeatable)
55+
--target <target> set runtime target (web | node)
56+
--dts emit declaration files (use --no-dts to disable)
57+
--externals <pkg> add package to externals (repeatable)
58+
--minify minify output (use --no-minify to disable)
59+
--clean clean output directory before build (use --no-clean to disable)
60+
--auto-extension control automatic extension redirect (use --no-auto-extension to disable)
61+
--auto-external control automatic dependency externalization (use --no-auto-external to disable)
62+
--tsconfig <path> use specific tsconfig (relative to project root)
5063
```
5164

5265
### Environment variables

website/docs/zh/config/rsbuild/source.mdx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ import { RsbuildDocBadge } from '@components/RsbuildDocBadge';
2020

2121
## source.entry <RsbuildDocBadge path="/config/source/entry" text="source.entry" />
2222

23-
- **命令行:** `--entry <path>`(可重复,例如 `--entry index=src/index.ts``--entry src/index.ts`
23+
- **命令行:** `--entry <path>`。可重复,例如 `--entry index=src/index.ts``--entry src/index.ts`
24+
25+
命令行参数支持两种格式:
26+
1. `key=value`:显式指定入口名称,例如 `--entry main=src/main.ts`
27+
2. `value`:仅指定路径,此时会自动使用文件的 basename(不含扩展名)作为入口名称。例如 `--entry src/index.ts` 会生成名为 `index` 的入口。如果存在同名冲突,则会为所有冲突的入口添加数字后缀(从 0 开始),例如 `index0``index1`
2428

2529
用于设置构建的入口模块。
2630

@@ -43,8 +47,6 @@ const defaultEntry = {
4347
};
4448
```
4549

46-
当未显式指定名称时,CLI 会自动使用 `index``entry2` 等占位名称。
47-
4850
:::info
4951
参考 [lib.bundle](/config/lib/bundle#set-entry) 进一步了解如何为 bundle 和 bundleless 项目设置入口。
5052
:::

0 commit comments

Comments
 (0)