diff --git a/packages/core/src/build.ts b/packages/core/src/build.ts index a4894f95b..d689cb65b 100644 --- a/packages/core/src/build.ts +++ b/packages/core/src/build.ts @@ -1,13 +1,18 @@ -import type { RsbuildInstance } from '@rsbuild/core'; +import { type RsbuildInstance, createRsbuild } from '@rsbuild/core'; import type { BuildOptions } from './cli/commands'; -import { initRsbuild } from './config'; +import { composeRsbuildEnvironments, pruneEnvironments } from './config'; import type { RslibConfig } from './types/config'; export async function build( config: RslibConfig, options?: BuildOptions, ): Promise { - const rsbuildInstance = await initRsbuild(config); + const environments = await composeRsbuildEnvironments(config); + const rsbuildInstance = await createRsbuild({ + rsbuildConfig: { + environments: pruneEnvironments(environments, options?.lib), + }, + }); await rsbuildInstance.build({ watch: options?.watch, diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 49c10f7e6..3c0df6209 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -1,12 +1,17 @@ -import type { RsbuildMode } from '@rsbuild/core'; +import { type RsbuildMode, createRsbuild } from '@rsbuild/core'; import { type Command, program } from 'commander'; import { build } from '../build'; -import { initRsbuild, loadConfig } from '../config'; +import { + composeRsbuildEnvironments, + loadConfig, + pruneEnvironments, +} from '../config'; import { logger } from '../utils/logger'; export type CommonOptions = { config?: string; envMode?: string; + lib?: string[]; }; export type BuildOptions = CommonOptions & { @@ -31,6 +36,10 @@ const applyCommonOptions = (command: Command) => { ); }; +const repeatableOption = (value: string, previous: string[]) => { + return (previous ?? []).concat([value]); +}; + export function runCli(): void { program.name('rslib').usage(' [options]').version(RSLIB_VERSION); @@ -40,6 +49,11 @@ export function runCli(): void { [buildCommand, inspectCommand].forEach(applyCommonOptions); buildCommand + .option( + '--lib ', + 'build the specified library (may be repeated)', + repeatableOption, + ) .option('-w --watch', 'turn on watch mode, watch for changes and rebuild') .description('build the library for production') .action(async (options: BuildOptions) => { @@ -58,6 +72,11 @@ export function runCli(): void { inspectCommand .description('inspect the Rsbuild / Rspack configs of Rslib projects') + .option( + '--lib ', + 'inspect the specified library (may be repeated)', + repeatableOption, + ) .option( '--output ', 'specify inspect content output path', @@ -71,7 +90,12 @@ export function runCli(): void { path: options.config, envMode: options.envMode, }); - const rsbuildInstance = await initRsbuild(rslibConfig); + const environments = await composeRsbuildEnvironments(rslibConfig); + const rsbuildInstance = await createRsbuild({ + rsbuildConfig: { + environments: pruneEnvironments(environments, options.lib), + }, + }); await rsbuildInstance.inspectConfig({ mode: options.mode, verbose: options.verbose, diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index ac109619a..a0d57763f 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,9 +1,8 @@ import fs from 'node:fs'; import path, { dirname, extname, isAbsolute, join } from 'node:path'; import { + type EnvironmentConfig, type RsbuildConfig, - type RsbuildInstance, - createRsbuild, defineConfig as defineRsbuildConfig, loadConfig as loadRsbuildConfig, mergeRsbuildConfig, @@ -1191,9 +1190,9 @@ export async function composeCreateRsbuildConfig( return composedRsbuildConfig; } -export async function initRsbuild( +export async function composeRsbuildEnvironments( rslibConfig: RslibConfig, -): Promise { +): Promise> { const rsbuildConfigObject = await composeCreateRsbuildConfig(rslibConfig); const environments: RsbuildConfig['environments'] = {}; const formatCount: Record = rsbuildConfigObject.reduce( @@ -1220,9 +1219,18 @@ export async function initRsbuild( ] = config; } - return createRsbuild({ - rsbuildConfig: { - environments, - }, - }); + return environments; } + +export const pruneEnvironments = ( + environments: Record, + libs?: string[], +): Record => { + if (!libs) { + return environments; + } + + return Object.fromEntries( + Object.entries(environments).filter(([name]) => libs.includes(name)), + ); +}; diff --git a/tests/integration/cli/build.test.ts b/tests/integration/cli/build.test.ts new file mode 100644 index 000000000..3ff9ba998 --- /dev/null +++ b/tests/integration/cli/build.test.ts @@ -0,0 +1,54 @@ +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import fse from 'fs-extra'; +import { globContentJSON } from 'test-helper'; +import { describe, expect, test } from 'vitest'; + +describe('build command', async () => { + test('basic', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync('npx rslib build', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist')); + const fileNames = Object.keys(files).sort(); + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/cjs/index.cjs", + "/tests/integration/cli/dist/esm/index.js", + ] + `); + }); + + test('--lib', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync('npx rslib build --lib esm', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist')); + const fileNames = Object.keys(files).sort(); + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/esm/index.js", + ] + `); + }); + + test('--lib multiple', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync('npx rslib build --lib esm --lib cjs', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist')); + const fileNames = Object.keys(files).sort(); + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/cjs/index.cjs", + "/tests/integration/cli/dist/esm/index.js", + ] + `); + }); +}); diff --git a/tests/integration/cli/index.test.ts b/tests/integration/cli/index.test.ts deleted file mode 100644 index b9abc7555..000000000 --- a/tests/integration/cli/index.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { execSync } from 'node:child_process'; -import path from 'node:path'; -import fse from 'fs-extra'; -import { globContentJSON } from 'test-helper'; -import { expect, test } from 'vitest'; - -test.todo('build command', async () => {}); - -test('inspect command', async () => { - await fse.remove(path.join(__dirname, 'dist')); - execSync('npx rslib inspect', { - cwd: __dirname, - }); - - const files = await globContentJSON(path.join(__dirname, 'dist/.rsbuild')); - const fileNames = Object.keys(files).sort(); - - expect(fileNames).toMatchInlineSnapshot(` - [ - "/tests/integration/cli/dist/.rsbuild/rsbuild.config.cjs.mjs", - "/tests/integration/cli/dist/.rsbuild/rsbuild.config.esm.mjs", - "/tests/integration/cli/dist/.rsbuild/rspack.config.cjs.mjs", - "/tests/integration/cli/dist/.rsbuild/rspack.config.esm.mjs", - ] - `); - - // esm rsbuild config - const rsbuildConfigEsm = fileNames.find((item) => - item.includes('rsbuild.config.esm.mjs'), - ); - expect(rsbuildConfigEsm).toBeTruthy(); - expect(files[rsbuildConfigEsm!]).toContain("type: 'modern-module'"); - - // esm rspack config - const rspackConfigEsm = fileNames.find((item) => - item.includes('rspack.config.esm.mjs'), - ); - expect(rspackConfigEsm).toBeTruthy(); - expect(files[rspackConfigEsm!]).toContain("type: 'modern-module'"); -}); diff --git a/tests/integration/cli/inspect.test.ts b/tests/integration/cli/inspect.test.ts new file mode 100644 index 000000000..88b2a71dc --- /dev/null +++ b/tests/integration/cli/inspect.test.ts @@ -0,0 +1,95 @@ +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import { describe } from 'node:test'; +import fse from 'fs-extra'; +import { globContentJSON } from 'test-helper'; +import { expect, test } from 'vitest'; + +describe('inspect command', async () => { + test('basic', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync('npx rslib inspect', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist/.rsbuild')); + const fileNames = Object.keys(files).sort(); + + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/.rsbuild/rsbuild.config.cjs.mjs", + "/tests/integration/cli/dist/.rsbuild/rsbuild.config.esm.mjs", + "/tests/integration/cli/dist/.rsbuild/rspack.config.cjs.mjs", + "/tests/integration/cli/dist/.rsbuild/rspack.config.esm.mjs", + ] + `); + + // esm rsbuild config + const rsbuildConfigEsm = fileNames.find((item) => + item.includes('rsbuild.config.esm.mjs'), + ); + expect(rsbuildConfigEsm).toBeTruthy(); + expect(files[rsbuildConfigEsm!]).toContain("type: 'modern-module'"); + + // esm rspack config + const rspackConfigEsm = fileNames.find((item) => + item.includes('rspack.config.esm.mjs'), + ); + expect(rspackConfigEsm).toBeTruthy(); + expect(files[rspackConfigEsm!]).toContain("type: 'modern-module'"); + }); + + test('--lib', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync('npx rslib inspect --lib esm', { + cwd: __dirname, + }); + + const files = await globContentJSON( + path.join(__dirname, 'dist/esm/.rsbuild'), + ); + const fileNames = Object.keys(files).sort(); + + // Rsbuild will emit dump files to `dist/esm` if only one environment is specified. + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/esm/.rsbuild/rsbuild.config.mjs", + "/tests/integration/cli/dist/esm/.rsbuild/rspack.config.esm.mjs", + ] + `); + + // esm rsbuild config + const rsbuildConfigEsm = fileNames.find((item) => + item.includes('rsbuild.config.mjs'), + ); + expect(rsbuildConfigEsm).toBeTruthy(); + expect(files[rsbuildConfigEsm!]).toContain("type: 'modern-module'"); + + // esm rspack config + const rspackConfigEsm = fileNames.find((item) => + item.includes('rspack.config.esm.mjs'), + ); + expect(rspackConfigEsm).toBeTruthy(); + expect(files[rspackConfigEsm!]).toContain("type: 'modern-module'"); + }); + + test('--lib multiple', async () => { + await fse.remove(path.join(__dirname, 'dist')); + execSync('npx rslib inspect --lib esm --lib cjs', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist/.rsbuild')); + const fileNames = Object.keys(files).sort(); + + // Rsbuild will emit dump files to `dist/esm` if only one environment is specified. + expect(fileNames).toMatchInlineSnapshot(` + [ + "/tests/integration/cli/dist/.rsbuild/rsbuild.config.cjs.mjs", + "/tests/integration/cli/dist/.rsbuild/rsbuild.config.esm.mjs", + "/tests/integration/cli/dist/.rsbuild/rspack.config.cjs.mjs", + "/tests/integration/cli/dist/.rsbuild/rspack.config.esm.mjs", + ] + `); + }); +}); diff --git a/tests/integration/cli/src/index.ts b/tests/integration/cli/src/index.ts new file mode 100644 index 000000000..3329a7d97 --- /dev/null +++ b/tests/integration/cli/src/index.ts @@ -0,0 +1 @@ +export const foo = 'foo'; diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index ba89de945..c455f6c30 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 --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 -h, --help display help for command ``` @@ -58,6 +59,7 @@ inspect the Rsbuild / Rspack configs of Rslib projects Options: -c --config specify the configuration file, can be a relative or absolute path --env-mode specify the env mode to load the `.env.[mode]` file + --lib build the specified library (may be repeated) --output specify inspect content output path (default: ".rsbuild") --verbose show full function definitions in output -h, --help display help for command