Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/__mocks__/rslog.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ module.exports = {
logger: {
warn: () => {},
override: () => {},
debug: () => {},
},
};
58 changes: 52 additions & 6 deletions packages/core/src/cli/commands.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { LogLevel, RsbuildMode } from '@rsbuild/core';
import cac, { type CAC } from 'cac';
import type { ConfigLoader } from '../config';
import type { Format, Syntax } from '../types/config';
import { logger } from '../utils/logger';
import { build } from './build';
import { init } from './init';
import { initConfig } from './initConfig';
import { inspect } from './inspect';
import { startMFDevServer } from './mf';
import { watchFilesForRestart } from './restart';
Expand All @@ -20,6 +21,19 @@ export type CommonOptions = {

export type BuildOptions = CommonOptions & {
watch?: boolean;
format?: Format;
entry?: string[];
distPath?: string;
bundle?: boolean;
syntax?: Syntax;
target?: string;
dts?: boolean;
externals?: string[];
minify?: boolean;
clean?: boolean;
autoExtension?: boolean;
autoExternal?: boolean;
tsconfig?: string;
};

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

buildCommand
.option('-w, --watch', 'turn on watch mode, watch for changes and rebuild')
.option('--entry <entry>', 'set entry file or pattern (repeatable)', {
type: [String],
default: [],
})
.option('--dist-path <dir>', 'set output directory')
.option('--bundle', 'enable bundle mode (use --no-bundle to disable)')
.option(
'--format <format>',
'specify the output format (esm | cjs | umd | mf | iife)',
)
.option('--syntax <syntax>', 'set build syntax target (repeatable)')
.option('--target <target>', 'set runtime target (web | node)')
.option('--dts', 'emit declaration files (use --no-dts to disable)')
.option('--externals <pkg>', 'add package to externals (repeatable)', {
type: [String],
default: [],
})
.option('--minify', 'minify output (use --no-minify to disable)')
.option(
'--clean',
'clean output directory before build (use --no-clean to disable)',
)
.option(
'--auto-extension',
'control automatic extension redirect (use --no-auto-extension to disable)',
)
.option(
'--auto-external',
'control automatic dependency externalization (use --no-auto-external to disable)',
)
.option(
'--tsconfig <path>',
'use specific tsconfig (relative to project root)',
)
.action(async (options: BuildOptions) => {
try {
const cliBuild = async () => {
const { config, watchFiles } = await init(options);

const { config, watchFiles } = await initConfig(options);
await build(config, options);

if (options.watch) {
watchFilesForRestart(watchFiles, async () => {
await cliBuild();
Expand Down Expand Up @@ -120,7 +166,7 @@ export function runCli(): void {
.action(async (options: InspectOptions) => {
try {
// TODO: inspect should output Rslib's config
const { config } = await init(options);
const { config } = await initConfig(options);
await inspect(config, {
lib: options.lib,
mode: options.mode,
Expand All @@ -137,7 +183,7 @@ export function runCli(): void {
mfDevCommand.action(async (options: CommonOptions) => {
try {
const cliMfDev = async () => {
const { config, watchFiles } = await init(options);
const { config, watchFiles } = await initConfig(options);
await startMFDevServer(config, {
lib: options.lib,
});
Expand Down
56 changes: 0 additions & 56 deletions packages/core/src/cli/init.ts

This file was deleted.

141 changes: 141 additions & 0 deletions packages/core/src/cli/initConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import path from 'node:path';
import util from 'node:util';
import { loadEnv, type RsbuildEntry } from '@rsbuild/core';
import { loadConfig } from '../config';
import type { RsbuildConfigOutputTarget, RslibConfig } from '../types';
import { getAbsolutePath } from '../utils/helper';
import { logger } from '../utils/logger';
import type { BuildOptions, CommonOptions } from './commands';
import { onBeforeRestart } from './restart';

const getEnvDir = (cwd: string, envDir?: string) => {
if (envDir) {
return path.isAbsolute(envDir) ? envDir : path.resolve(cwd, envDir);
}
return cwd;
};

export const parseEntryOption = (
entries?: string[],
): Record<string, string> | undefined => {
if (!entries?.length) return undefined;

const entryList: { key: string; value: string; explicit: boolean }[] = [];

for (const rawEntry of entries) {
const value = rawEntry?.trim();
if (!value) continue;

const equalIndex = value.indexOf('=');
if (equalIndex > -1) {
const name = value.slice(0, equalIndex).trim();
const entryPath = value.slice(equalIndex + 1).trim();
if (name && entryPath) {
entryList.push({ key: name, value: entryPath, explicit: true });
continue;
}
}

const basename = path.basename(value, path.extname(value));
entryList.push({ key: basename, value, explicit: false });
}

const keyCount: Record<string, number> = {};
for (const { key, explicit } of entryList) {
if (!explicit) keyCount[key] = (keyCount[key] ?? 0) + 1;
}

const keyIndex: Record<string, number> = {};
const parsed: Record<string, string> = {};

for (const { key, value, explicit } of entryList) {
const needsIndex = !explicit && (keyCount[key] ?? 0) > 1;
const finalKey = needsIndex ? `${key}${keyIndex[key] ?? 0}` : key;
if (needsIndex) keyIndex[key] = (keyIndex[key] ?? 0) + 1;
parsed[finalKey] = value;
}

return Object.keys(parsed).length ? parsed : undefined;
};

export const applyCliOptions = (
config: RslibConfig,
options: BuildOptions,
root: string,
): void => {
if (options.root) config.root = root;
if (options.logLevel) config.logLevel = options.logLevel;

for (const lib of config.lib) {
if (options.format !== undefined) lib.format = options.format;
if (options.bundle !== undefined) lib.bundle = options.bundle;
if (options.tsconfig !== undefined) {
lib.source ||= {};
lib.source.tsconfigPath = options.tsconfig;
}
const entry = parseEntryOption(options.entry);
if (entry !== undefined) {
lib.source ||= {};
lib.source.entry = entry as RsbuildEntry;
}
const syntax = options.syntax;
if (syntax !== undefined) lib.syntax = syntax;
if (options.dts !== undefined) lib.dts = options.dts;
if (options.autoExtension !== undefined)
lib.autoExtension = options.autoExtension;
if (options.autoExternal !== undefined)
lib.autoExternal = options.autoExternal;
const output = lib.output ?? {};
if (options.target !== undefined)
output.target = options.target as RsbuildConfigOutputTarget;
if (options.minify !== undefined) output.minify = options.minify;
if (options.clean !== undefined) output.cleanDistPath = options.clean;
const externals = options.externals?.filter(Boolean) ?? [];
if (externals.length > 0) output.externals = externals;
if (options.distPath) {
output.distPath = {
...(typeof output.distPath === 'object' ? output.distPath : {}),
root: options.distPath,
};
}
}
};

export async function initConfig(options: CommonOptions): Promise<{
config: RslibConfig;
configFilePath: string;
watchFiles: string[];
}> {
const cwd = process.cwd();
const root = options.root ? getAbsolutePath(cwd, options.root) : cwd;
const envs = loadEnv({
cwd: getEnvDir(root, options.envDir),
mode: options.envMode,
});

onBeforeRestart(envs.cleanup);

const { content: config, filePath: configFilePath } = await loadConfig({
cwd: root,
path: options.config,
envMode: options.envMode,
loader: options.configLoader,
});

config.source ||= {};
config.source.define = {
...envs.publicVars,
...config.source.define,
};

applyCliOptions(config, options, root);

logger.debug('Rslib config used to generate Rsbuild environments:');
logger.debug(`\n${util.inspect(config, { depth: null, colors: true })}`);

return {
config,
configFilePath,
watchFiles: [configFilePath, ...envs.filePaths],
};
}
Loading
Loading