Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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