diff --git a/packages/core/src/build.ts b/packages/core/src/cli/build.ts similarity index 62% rename from packages/core/src/build.ts rename to packages/core/src/cli/build.ts index d689cb65b..13386b8ba 100644 --- a/packages/core/src/build.ts +++ b/packages/core/src/cli/build.ts @@ -1,21 +1,21 @@ import { type RsbuildInstance, createRsbuild } from '@rsbuild/core'; -import type { BuildOptions } from './cli/commands'; -import { composeRsbuildEnvironments, pruneEnvironments } from './config'; -import type { RslibConfig } from './types/config'; +import { composeRsbuildEnvironments, pruneEnvironments } from '../config'; +import type { RslibConfig } from '../types/config'; +import type { BuildOptions } from './commands'; export async function build( config: RslibConfig, - options?: BuildOptions, + options: Pick = {}, ): Promise { const environments = await composeRsbuildEnvironments(config); const rsbuildInstance = await createRsbuild({ rsbuildConfig: { - environments: pruneEnvironments(environments, options?.lib), + environments: pruneEnvironments(environments, options.lib), }, }); await rsbuildInstance.build({ - watch: options?.watch, + watch: options.watch, }); return rsbuildInstance; diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 07af6cc2d..11714be33 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -1,15 +1,13 @@ -import { type RsbuildMode, createRsbuild } from '@rsbuild/core'; +import type { RsbuildMode } from '@rsbuild/core'; import { type Command, program } from 'commander'; -import { build } from '../build'; -import { - composeRsbuildEnvironments, - loadConfig, - pruneEnvironments, -} from '../config'; -import { startMFDevServer } from '../mf'; import { logger } from '../utils/logger'; +import { build } from './build'; +import { loadRslibConfig } from './init'; +import { inspect } from './inspect'; +import { startMFDevServer } from './mf'; export type CommonOptions = { + root?: string; config?: string; envMode?: string; lib?: string[]; @@ -20,8 +18,8 @@ export type BuildOptions = CommonOptions & { }; export type InspectOptions = CommonOptions & { - mode: RsbuildMode; - output: string; + mode?: RsbuildMode; + output?: string; verbose?: boolean; }; @@ -31,6 +29,10 @@ const applyCommonOptions = (command: Command) => { '-c --config ', 'specify the configuration file, can be a relative or absolute path', ) + .option( + '-r --root ', + 'specify the project root directory, can be an absolute path or a path relative to cwd', + ) .option( '--env-mode ', 'specify the env mode to load the `.env.[mode]` file', @@ -60,11 +62,11 @@ export function runCli(): void { .description('build the library for production') .action(async (options: BuildOptions) => { try { - const rslibConfig = await loadConfig({ - path: options.config, - envMode: options.envMode, + const rslibConfig = await loadRslibConfig(options); + await build(rslibConfig, { + lib: options.lib, + watch: options.watch, }); - await build(rslibConfig, options); } catch (err) { logger.error('Failed to build.'); logger.error(err); @@ -88,21 +90,12 @@ export function runCli(): void { .action(async (options: InspectOptions) => { try { // TODO: inspect should output Rslib's config - const rslibConfig = await loadConfig({ - path: options.config, - envMode: options.envMode, - }); - const environments = await composeRsbuildEnvironments(rslibConfig); - const rsbuildInstance = await createRsbuild({ - rsbuildConfig: { - environments: pruneEnvironments(environments, options.lib), - }, - }); - await rsbuildInstance.inspectConfig({ + const rslibConfig = await loadRslibConfig(options); + await inspect(rslibConfig, { + lib: options.lib, mode: options.mode, + output: options.output, verbose: options.verbose, - outputPath: options.output, - writeToDisk: true, }); } catch (err) { logger.error('Failed to inspect config.'); @@ -115,10 +108,8 @@ export function runCli(): void { .description('start Rsbuild dev server of Module Federation format') .action(async (options: CommonOptions) => { try { - const rslibConfig = await loadConfig({ - path: options.config, - envMode: options.envMode, - }); + const rslibConfig = await loadRslibConfig(options); + // TODO: support lib option in mf dev server await startMFDevServer(rslibConfig); } catch (err) { logger.error('Failed to start mf dev.'); diff --git a/packages/core/src/cli/init.ts b/packages/core/src/cli/init.ts new file mode 100644 index 000000000..e77035b2d --- /dev/null +++ b/packages/core/src/cli/init.ts @@ -0,0 +1,19 @@ +import { loadConfig } from '../config'; +import type { RslibConfig } from '../types'; +import { getAbsolutePath } from '../utils/helper'; +import type { CommonOptions } from './commands'; + +export async function loadRslibConfig( + options: CommonOptions, +): Promise { + const cwd = process.cwd(); + const root = options.root ? getAbsolutePath(cwd, options.root) : cwd; + + const rslibConfig = await loadConfig({ + cwd: root, + path: options.config, + envMode: options.envMode, + }); + + return rslibConfig; +} diff --git a/packages/core/src/cli/inspect.ts b/packages/core/src/cli/inspect.ts new file mode 100644 index 000000000..cfe67a6c7 --- /dev/null +++ b/packages/core/src/cli/inspect.ts @@ -0,0 +1,25 @@ +import { type RsbuildInstance, createRsbuild } from '@rsbuild/core'; +import { composeRsbuildEnvironments, pruneEnvironments } from '../config'; +import type { RslibConfig } from '../types/config'; +import type { InspectOptions } from './commands'; + +export async function inspect( + config: RslibConfig, + options: Pick = {}, +): Promise { + const environments = await composeRsbuildEnvironments(config); + const rsbuildInstance = await createRsbuild({ + rsbuildConfig: { + environments: pruneEnvironments(environments, options.lib), + }, + }); + + await rsbuildInstance.inspectConfig({ + mode: options.mode, + verbose: options.verbose, + outputPath: options.output, + writeToDisk: true, + }); + + return rsbuildInstance; +} diff --git a/packages/core/src/mf.ts b/packages/core/src/cli/mf.ts similarity index 92% rename from packages/core/src/mf.ts rename to packages/core/src/cli/mf.ts index 4412cc0cd..352f89f01 100644 --- a/packages/core/src/mf.ts +++ b/packages/core/src/cli/mf.ts @@ -1,8 +1,7 @@ import { createRsbuild, mergeRsbuildConfig } from '@rsbuild/core'; -import { composeCreateRsbuildConfig } from './config'; - import type { RsbuildConfig, RsbuildInstance } from '@rsbuild/core'; -import type { RslibConfig } from './types'; +import { composeCreateRsbuildConfig } from '../config'; +import type { RslibConfig } from '../types'; export async function startMFDevServer( config: RslibConfig, diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index cc056b55f..eaaf14efa 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -53,6 +53,7 @@ import { calcLongestCommonPath, checkMFPlugin, color, + getAbsolutePath, isEmptyObject, isObject, nodeBuiltInModules, @@ -1084,9 +1085,13 @@ const composeExternalHelpersConfig = ( return defaultConfig; }; -async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { +async function composeLibRsbuildConfig(config: LibConfig) { checkMFPlugin(config); - const rootPath = dirname(configPath); + + // Get the absolute path of the root directory to align with Rsbuild's default behavior + const rootPath = config.root + ? getAbsolutePath(process.cwd(), config.root) + : process.cwd(); const pkgJson = readPackageJson(rootPath); const { compilerOptions } = await loadTsconfig( rootPath, @@ -1148,7 +1153,7 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { const { entryConfig, lcp } = await composeEntryConfig( config.source?.entry, config.bundle, - dirname(configPath), + rootPath, cssModulesAuto, ); const cssConfig = composeCssConfig(lcp, config.bundle); @@ -1192,10 +1197,8 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { export async function composeCreateRsbuildConfig( rslibConfig: RslibConfig, - path?: string, ): Promise { const constantRsbuildConfig = await createConstantRsbuildConfig(); - const configPath = path ?? rslibConfig._privateMeta?.configFilePath!; const { lib: libConfigsArray, ...sharedRsbuildConfig } = rslibConfig; if (!libConfigsArray) { @@ -1212,10 +1215,7 @@ export async function composeCreateRsbuildConfig( // Merge the configuration of each environment based on the shared Rsbuild // configuration and Lib configuration in the settings. - const libRsbuildConfig = await composeLibRsbuildConfig( - userConfig, - configPath, - ); + const libRsbuildConfig = await composeLibRsbuildConfig(userConfig); // Reset certain fields because they will be completely overridden by the upcoming merge. // We don't want to retain them in the final configuration. @@ -1271,12 +1271,9 @@ export async function composeCreateRsbuildConfig( export async function composeRsbuildEnvironments( rslibConfig: RslibConfig, - path?: string, ): Promise> { - const rsbuildConfigWithLibInfo = await composeCreateRsbuildConfig( - rslibConfig, - path, - ); + const rsbuildConfigWithLibInfo = + await composeCreateRsbuildConfig(rslibConfig); // User provided ids should take precedence over generated ids. const usedIds = rsbuildConfigWithLibInfo diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1cbdf6844..ad9999f3d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,11 +1,13 @@ export { prepareCli } from './cli/prepare'; export { runCli } from './cli/commands'; +export { build } from './cli/build'; +export { inspect } from './cli/inspect'; +export { startMFDevServer } from './cli/mf'; export { defineConfig, loadConfig, composeCreateRsbuildConfig as unstable_composeCreateRsbuildConfig, } from './config'; -export { build } from './build'; export { logger } from './utils/logger'; export type * from './types'; diff --git a/packages/core/src/utils/helper.ts b/packages/core/src/utils/helper.ts index 9ead63e39..d0cde5bc2 100644 --- a/packages/core/src/utils/helper.ts +++ b/packages/core/src/utils/helper.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; import fsP from 'node:fs/promises'; -import path from 'node:path'; +import path, { isAbsolute, join } from 'node:path'; import type { RsbuildPlugins } from '@rsbuild/core'; import color from 'picocolors'; @@ -109,6 +109,10 @@ export async function calcLongestCommonPath( return lca; } +export function getAbsolutePath(base: string, filepath: string): string { + return isAbsolute(filepath) ? filepath : join(base, filepath); +} + export const readPackageJson = (rootPath: string): undefined | PkgJson => { const pkgJsonPath = path.join(rootPath, './package.json'); diff --git a/packages/core/tests/__snapshots__/config.test.ts.snap b/packages/core/tests/__snapshots__/config.test.ts.snap index f21376628..b2a5c77b8 100644 --- a/packages/core/tests/__snapshots__/config.test.ts.snap +++ b/packages/core/tests/__snapshots__/config.test.ts.snap @@ -72,7 +72,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config 1 "pnpapi", ], "filename": { - "js": "[name].js", + "js": "[name].mjs", }, "filenameHash": false, "minify": { diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index aeb2f42ab..d0516958c 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -193,10 +193,7 @@ describe('Should compose create Rsbuild config correctly', () => { }, }, }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect(composedRsbuildConfig).toMatchSnapshot(); }); }); @@ -211,10 +208,7 @@ describe('syntax', () => { ], }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect( composedRsbuildConfig[0].config.output?.overrideBrowserslist, @@ -237,10 +231,7 @@ describe('syntax', () => { }, }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect( composedRsbuildConfig[0].config.output?.overrideBrowserslist, @@ -268,10 +259,7 @@ describe('syntax', () => { }, }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect( composedRsbuildConfig[0].config.output?.overrideBrowserslist, @@ -292,10 +280,7 @@ describe('syntax', () => { ], }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect( composedRsbuildConfig[0].config.output?.overrideBrowserslist, @@ -323,10 +308,7 @@ describe('minify', () => { ], }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect( composedRsbuildConfig[0].config.output?.minify, @@ -383,10 +365,7 @@ describe('minify', () => { }, }; - const composedRsbuildConfig = await composeCreateRsbuildConfig( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeCreateRsbuildConfig(rslibConfig); expect( composedRsbuildConfig[0].config.output?.minify, @@ -429,10 +408,7 @@ describe('id', () => { ], }; - const composedRsbuildConfig = await composeRsbuildEnvironments( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeRsbuildEnvironments(rslibConfig); expect(Object.keys(composedRsbuildConfig)).toMatchInlineSnapshot(` [ @@ -469,10 +445,7 @@ describe('id', () => { ], }; - const composedRsbuildConfig = await composeRsbuildEnvironments( - rslibConfig, - process.cwd(), - ); + const composedRsbuildConfig = await composeRsbuildEnvironments(rslibConfig); expect(Object.keys(composedRsbuildConfig)).toMatchInlineSnapshot(` [ "esm1", @@ -512,9 +485,8 @@ describe('id', () => { ], }; - // await composeRsbuildEnvironments(rslibConfig, process.cwd()); await expect(() => - composeRsbuildEnvironments(rslibConfig, process.cwd()), + composeRsbuildEnvironments(rslibConfig), ).rejects.toThrowError( 'The following ids are duplicated: "a", "b". Please change the "lib.id" to be unique.', ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 157bdf184..781e08b7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -526,6 +526,8 @@ importers: tests/integration/cli: {} + tests/integration/cli/custom-root: {} + tests/integration/copy: {} tests/integration/decorators/default: {} diff --git a/tests/integration/cli/build.test.ts b/tests/integration/cli/build.test.ts index 3ff9ba998..98ee5f6be 100644 --- a/tests/integration/cli/build.test.ts +++ b/tests/integration/cli/build.test.ts @@ -51,4 +51,40 @@ describe('build command', async () => { ] `); }); + + test('--config', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync( + 'npx rslib build --config ./custom-config/rslib.config.custom.ts', + { + cwd: __dirname, + }, + ); + + const files = await globContentJSON(path.join(__dirname, 'dist')); + const fileNames = Object.keys(files).sort(); + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/custom/index.cjs", + "/tests/integration/cli/dist/custom/index.js", + ] + `); + }); + + test('--root', async () => { + await fse.remove(path.join(__dirname, 'dist')); + console.log('__dirname: ', __dirname); + execSync('npx rslib build --root custom-root', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist')); + const fileNames = Object.keys(files).sort(); + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/root/index.cjs", + "/tests/integration/cli/dist/root/index.js", + ] + `); + }); }); diff --git a/tests/integration/cli/custom-config/rslib.config.custom.ts b/tests/integration/cli/custom-config/rslib.config.custom.ts new file mode 100644 index 000000000..8e321fa46 --- /dev/null +++ b/tests/integration/cli/custom-config/rslib.config.custom.ts @@ -0,0 +1,21 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + output: { + distPath: { + root: './dist/custom', + }, + }, + }), + generateBundleCjsConfig({ + output: { + distPath: { + root: './dist/custom', + }, + }, + }), + ], +}); diff --git a/tests/integration/cli/custom-root/package.json b/tests/integration/cli/custom-root/package.json new file mode 100644 index 000000000..e7dfcc041 --- /dev/null +++ b/tests/integration/cli/custom-root/package.json @@ -0,0 +1,6 @@ +{ + "name": "cli-custom-root-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/cli/custom-root/rslib.config.ts b/tests/integration/cli/custom-root/rslib.config.ts new file mode 100644 index 000000000..3285ebec0 --- /dev/null +++ b/tests/integration/cli/custom-root/rslib.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + output: { + distPath: { + root: './dist/root', + }, + }, + }), + generateBundleCjsConfig({ + output: { + distPath: { + root: './dist/root', + }, + }, + }), + ], +}); diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index ec170bdd3..e0fe2e31e 100644 --- a/website/docs/en/guide/basic/cli.mdx +++ b/website/docs/en/guide/basic/cli.mdx @@ -37,6 +37,7 @@ build the library for production Options: -c --config specify the configuration file, can be a relative or absolute path + -r --root specify the project root directory, can be an absolute path or a path relative to cwd --env-mode specify the env mode to load the `.env.[mode]` file --lib build the specified library (may be repeated) -w --watch turn on watch mode, watch for changes and rebuild @@ -62,6 +63,7 @@ inspect the Rsbuild / Rspack configs of Rslib projects Options: -c --config specify the configuration file, can be a relative or absolute path + -r --root specify the project root directory, can be an absolute path or a path relative to cwd --env-mode specify the env mode to load the `.env.[mode]` file --lib inspect the specified library (may be repeated) --output specify inspect content output path (default: ".rsbuild") @@ -123,6 +125,7 @@ start Rsbuild dev server of Module Federation format Options: -c --config specify the configuration file, can be a relative or absolute path + -r --root specify the project root directory, can be an absolute path or a path relative to cwd --env-mode specify the env mode to load the `.env.[mode]` file -h, --help display help for command ```