diff --git a/examples/module-federation/mf-react-component/package.json b/examples/module-federation/mf-react-component/package.json index 90b44b140..eea15a0a7 100644 --- a/examples/module-federation/mf-react-component/package.json +++ b/examples/module-federation/mf-react-component/package.json @@ -13,7 +13,7 @@ "types": "./dist/cjs/index.d.ts", "scripts": { "build": "rslib build", - "dev": "rslib mf dev", + "dev": "rslib mf-dev", "serve": "pnpm build & http-server -p 3001 ./dist/ --cors", "storybook": "storybook dev -p 6006" }, diff --git a/packages/core/src/asset/assetConfig.ts b/packages/core/src/asset/assetConfig.ts index fdbf763e8..81598e6ea 100644 --- a/packages/core/src/asset/assetConfig.ts +++ b/packages/core/src/asset/assetConfig.ts @@ -1,11 +1,11 @@ -import type { RsbuildConfig } from '@rsbuild/core'; +import type { EnvironmentConfig } from '@rsbuild/core'; import type { Format } from '../types'; // TODO: asset config document export const composeAssetConfig = ( bundle: boolean, format: Format, -): RsbuildConfig => { +): EnvironmentConfig => { if (format === 'esm' || format === 'cjs') { if (bundle) { return { diff --git a/packages/core/src/cli/build.ts b/packages/core/src/cli/build.ts index a44ce4688..254f15a60 100644 --- a/packages/core/src/cli/build.ts +++ b/packages/core/src/cli/build.ts @@ -6,12 +6,15 @@ import { onBeforeRestart } from './restart'; export async function build( config: RslibConfig, - options: Pick = {}, + options: Pick = {}, ): Promise { - const environments = await composeRsbuildEnvironments(config); + const { environments } = await composeRsbuildEnvironments(config); const rsbuildInstance = await createRsbuild({ rsbuildConfig: { + mode: 'production', + root: config.root, plugins: config.plugins, + dev: config.dev, server: config.server, environments: pruneEnvironments(environments, options.lib), }, diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 0fe0323b1..195adeb0d 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -39,7 +39,12 @@ const applyCommonOptions = (command: Command) => { '--env-mode ', 'specify the env mode to load the `.env.[mode]` file', ) - .option('--env-dir ', 'specify the directory to load `.env` files'); + .option('--env-dir ', 'specify the directory to load `.env` files') + .option( + '--lib ', + 'specify the library (repeatable, e.g. --lib esm --lib cjs)', + repeatableOption, + ); }; const repeatableOption = (value: string, previous: string[]) => { @@ -51,16 +56,11 @@ export function runCli(): void { const buildCommand = program.command('build'); const inspectCommand = program.command('inspect'); - const mfDevCommand = program.command('mf dev'); + const mfDevCommand = program.command('mf-dev'); [buildCommand, inspectCommand, mfDevCommand].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) => { @@ -87,11 +87,6 @@ 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', @@ -121,8 +116,9 @@ export function runCli(): void { try { const cliMfDev = async () => { const { config, watchFiles } = await init(options); - // TODO: support lib option in mf dev server - await startMFDevServer(config); + await startMFDevServer(config, { + lib: options.lib, + }); watchFilesForRestart(watchFiles, async () => { await cliMfDev(); @@ -131,7 +127,7 @@ export function runCli(): void { await cliMfDev(); } catch (err) { - logger.error('Failed to start mf dev.'); + logger.error('Failed to start mf-dev.'); logger.error(err); process.exit(1); } diff --git a/packages/core/src/cli/inspect.ts b/packages/core/src/cli/inspect.ts index 350da1436..ace7b8a67 100644 --- a/packages/core/src/cli/inspect.ts +++ b/packages/core/src/cli/inspect.ts @@ -7,10 +7,13 @@ export async function inspect( config: RslibConfig, options: Pick = {}, ): Promise { - const environments = await composeRsbuildEnvironments(config); + const { environments } = await composeRsbuildEnvironments(config); const rsbuildInstance = await createRsbuild({ rsbuildConfig: { + mode: 'production', + root: config.root, plugins: config.plugins, + dev: config.dev, server: config.server, environments: pruneEnvironments(environments, options.lib), }, diff --git a/packages/core/src/cli/mf.ts b/packages/core/src/cli/mf.ts index 42d99e370..8dad91a3b 100644 --- a/packages/core/src/cli/mf.ts +++ b/packages/core/src/cli/mf.ts @@ -1,42 +1,63 @@ -import { createRsbuild, mergeRsbuildConfig } from '@rsbuild/core'; -import type { RsbuildConfig, RsbuildInstance } from '@rsbuild/core'; -import { composeCreateRsbuildConfig } from '../config'; +import { createRsbuild } from '@rsbuild/core'; +import type { RsbuildInstance } from '@rsbuild/core'; +import { composeRsbuildEnvironments, pruneEnvironments } from '../config'; import type { RslibConfig } from '../types'; +import type { CommonOptions } from './commands'; import { onBeforeRestart } from './restart'; export async function startMFDevServer( config: RslibConfig, + options: Pick = {}, ): Promise { - const rsbuildInstance = await initMFRsbuild(config); + const rsbuildInstance = await initMFRsbuild(config, options); return rsbuildInstance; } async function initMFRsbuild( - rslibConfig: RslibConfig, + config: RslibConfig, + options: Pick = {}, ): Promise { - const rsbuildConfigObject = await composeCreateRsbuildConfig(rslibConfig); - const mfRsbuildConfig = rsbuildConfigObject.find( - (config) => config.format === 'mf', - ); + const { environments, environmentWithInfos } = + await composeRsbuildEnvironments(config); - if (!mfRsbuildConfig) { - // no mf format, return. - return; + const selectedEnvironmentIds = environmentWithInfos + .filter((env) => { + const isMf = env.format === 'mf'; + if (!options?.lib) { + return isMf; + } + return env.id && options.lib.includes(env.id); + }) + .map((env) => env.id); + + if (!selectedEnvironmentIds.length) { + throw new Error('No mf format found, please check your config.'); } - mfRsbuildConfig.config = changeEnvToDev(mfRsbuildConfig.config); + const selectedEnvironments = pruneEnvironments( + environments, + selectedEnvironmentIds, + ); const rsbuildInstance = await createRsbuild({ rsbuildConfig: { - ...mfRsbuildConfig.config, - plugins: [ - ...(rslibConfig.plugins || []), - ...(mfRsbuildConfig.config.plugins || []), - ], - server: mergeRsbuildConfig( - rslibConfig.server, - mfRsbuildConfig.config.server, - ), + mode: 'development', + root: config.root, + plugins: config.plugins, + dev: { + ...(config.dev ?? {}), + writeToDisk: true, + }, + server: config.server, + tools: { + rspack: { + optimization: { + nodeEnv: 'development', + moduleIds: 'named', + }, + }, + }, + environments: selectedEnvironments, }, }); @@ -45,20 +66,3 @@ async function initMFRsbuild( onBeforeRestart(devServer.server.close); return rsbuildInstance; } - -function changeEnvToDev(rsbuildConfig: RsbuildConfig) { - return mergeRsbuildConfig(rsbuildConfig, { - mode: 'development', - dev: { - writeToDisk: true, - }, - tools: { - rspack: { - optimization: { - nodeEnv: 'development', - moduleIds: 'named', - }, - }, - }, - }); -} diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 7d9f23292..86c156bb1 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -44,6 +44,7 @@ import type { LibOnlyConfig, PkgJson, Redirect, + RequireKey, RsbuildConfigEntry, RsbuildConfigEntryItem, RsbuildConfigOutputTarget, @@ -141,13 +142,13 @@ export async function loadConfig({ const composeExternalsWarnConfig = ( format: Format, - ...externalsArray: NonNullable['externals'][] -): RsbuildConfig => { + ...externalsArray: NonNullable['externals'][] +): EnvironmentConfig => { if (format !== 'esm') { return {}; } - const externals: NonNullable['externals'] = []; + const externals: NonNullable['externals'] = []; for (const e of externalsArray.filter(Boolean)) { if (Array.isArray(e)) { externals.push(...e); @@ -159,7 +160,7 @@ const composeExternalsWarnConfig = ( // Match logic is derived from https://github.com/webpack/webpack/blob/94aba382eccf3de1004d235045d4462918dfdbb7/lib/ExternalModuleFactoryPlugin.js#L166-L293. const matchUserExternals = ( - externals: NonNullable['externals'], + externals: NonNullable['externals'], request: string, callback: (matched?: true) => void, ) => { @@ -252,8 +253,8 @@ export const composeAutoExternalConfig = (options: { format: Format; autoExternal?: AutoExternal; pkgJson?: PkgJson; - userExternals?: NonNullable['externals']; -}): RsbuildConfig => { + userExternals?: NonNullable['externals']; +}): EnvironmentConfig => { const { format, pkgJson, userExternals } = options; const autoExternal = getAutoExternalDefaultValue( @@ -317,7 +318,7 @@ export const composeAutoExternalConfig = (options: { : {}; }; -export function composeMinifyConfig(config: LibConfig): RsbuildConfig { +export function composeMinifyConfig(config: LibConfig): EnvironmentConfig { const minify = config.output?.minify; const format = config.format; if (minify !== undefined) { @@ -358,7 +359,7 @@ export function composeMinifyConfig(config: LibConfig): RsbuildConfig { export function composeBannerFooterConfig( banner: BannerAndFooter, footer: BannerAndFooter, -): RsbuildConfig { +): EnvironmentConfig { const bannerConfig = pick(banner, ['js', 'css']); const footerConfig = pick(footer, ['js', 'css']); @@ -428,9 +429,9 @@ export function composeBannerFooterConfig( export function composeDecoratorsConfig( compilerOptions?: Record, version?: NonNullable< - NonNullable['decorators'] + NonNullable['decorators'] >['version'], -): RsbuildConfig { +): EnvironmentConfig { if (version || !compilerOptions?.experimentalDecorators) { return {}; } @@ -444,9 +445,8 @@ export function composeDecoratorsConfig( }; } -export async function createConstantRsbuildConfig(): Promise { +export async function createConstantRsbuildConfig(): Promise { return defineRsbuildConfig({ - mode: 'production', dev: { progressBar: false, }, @@ -508,7 +508,7 @@ const composeFormatConfig = ({ pkgJson: PkgJson; bundle?: boolean; umdName?: string; -}): RsbuildConfig => { +}): EnvironmentConfig => { const jsParserOptions = { cjs: { requireResolve: false, @@ -583,7 +583,7 @@ const composeFormatConfig = ({ ); } - const config: RsbuildConfig = { + const config: EnvironmentConfig = { tools: { rspack: { module: { @@ -653,7 +653,7 @@ const formatRsbuildPlugin = (): RsbuildPlugin => ({ const composeShimsConfig = ( format: Format, shims?: Shims, -): { rsbuildConfig: RsbuildConfig; enabledShims: DeepRequired } => { +): { rsbuildConfig: EnvironmentConfig; enabledShims: DeepRequired } => { const resolvedShims = { cjs: { 'import.meta.url': shims?.cjs?.['import.meta.url'] ?? true, @@ -682,7 +682,7 @@ const composeShimsConfig = ( }, }; - let rsbuildConfig: RsbuildConfig = {}; + let rsbuildConfig: EnvironmentConfig = {}; switch (format) { case 'esm': { rsbuildConfig = { @@ -725,8 +725,8 @@ export const composeModuleImportWarn = (request: string): string => { const composeExternalsConfig = ( format: Format, - externals: NonNullable['externals'], -): RsbuildConfig => { + externals: NonNullable['externals'], +): EnvironmentConfig => { // TODO: Define the internal externals config in Rsbuild's externals instead // Rspack's externals as they will not be merged from different fields. All externals // should to be unified and merged together in the future. @@ -768,7 +768,7 @@ const composeAutoExtensionConfig = ( autoExtension: boolean, pkgJson?: PkgJson, ): { - config: RsbuildConfig; + config: EnvironmentConfig; jsExtension: string; dtsExtension: string; } => { @@ -778,7 +778,7 @@ const composeAutoExtensionConfig = ( autoExtension, }); - const updatedConfig: RsbuildConfig = { + const updatedConfig: EnvironmentConfig = { output: { filename: { js: `[name]${jsExtension}`, @@ -803,7 +803,7 @@ const composeAutoExtensionConfig = ( const composeSyntaxConfig = ( target: RsbuildConfigOutputTarget, syntax?: Syntax, -): RsbuildConfig => { +): EnvironmentConfig => { // Defaults to ESNext, Rslib will assume all of the latest JavaScript and CSS features are supported. if (syntax) { return { @@ -865,7 +865,7 @@ const composeEntryConfig = async ( bundle: LibConfig['bundle'], root: string, cssModulesAuto: CssLoaderOptionsAuto, -): Promise<{ entryConfig: RsbuildConfig; lcp: string | null }> => { +): Promise<{ entryConfig: EnvironmentConfig; lcp: string | null }> => { if (!entries) { return { entryConfig: {}, lcp: null }; } @@ -948,7 +948,7 @@ const composeEntryConfig = async ( } const lcp = await calcLongestCommonPath(Object.values(resolvedEntries)); - const entryConfig: RsbuildConfig = { + const entryConfig: EnvironmentConfig = { source: { entry: appendEntryQuery(resolvedEntries), }, @@ -966,7 +966,7 @@ const composeBundlelessExternalConfig = ( cssModulesAuto: CssLoaderOptionsAuto, bundle: boolean, ): { - config: RsbuildConfig; + config: EnvironmentConfig; resolvedJsRedirect?: DeepRequired; } => { if (bundle) return { config: {} }; @@ -1074,7 +1074,7 @@ const composeBundlelessExternalConfig = ( const composeDtsConfig = async ( libConfig: LibConfig, dtsExtension: string, -): Promise => { +): Promise => { const { format, autoExternal, banner, footer } = libConfig; let { dts } = libConfig; @@ -1110,8 +1110,8 @@ const composeTargetConfig = ( userTarget: RsbuildConfigOutputTarget, format: Format, ): { - config: RsbuildConfig; - externalsConfig: RsbuildConfig; + config: EnvironmentConfig; + externalsConfig: EnvironmentConfig; target: RsbuildConfigOutputTarget; } => { const target = userTarget ?? (format === 'mf' ? 'web' : 'node'); @@ -1204,14 +1204,13 @@ const composeExternalHelpersConfig = ( async function composeLibRsbuildConfig( config: LibConfig, + root?: string, sharedPlugins?: RsbuildPlugins, ) { checkMFPlugin(config, sharedPlugins); // 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 rootPath = root ? getAbsolutePath(process.cwd(), root) : process.cwd(); const pkgJson = readPackageJson(rootPath); const { compilerOptions } = await loadTsconfig( rootPath, @@ -1334,7 +1333,10 @@ export async function composeCreateRsbuildConfig( const constantRsbuildConfig = await createConstantRsbuildConfig(); const { lib: libConfigsArray, + mode, + root, plugins: sharedPlugins, + dev, server, ...sharedRsbuildConfig } = rslibConfig; @@ -1355,6 +1357,7 @@ export async function composeCreateRsbuildConfig( // configuration and Lib configuration in the settings. const libRsbuildConfig = await composeLibRsbuildConfig( userConfig, + root, sharedPlugins, ); @@ -1412,9 +1415,13 @@ export async function composeCreateRsbuildConfig( export async function composeRsbuildEnvironments( rslibConfig: RslibConfig, -): Promise> { +): Promise<{ + environments: Record; + environmentWithInfos: RequireKey[]; +}> { const rsbuildConfigWithLibInfo = await composeCreateRsbuildConfig(rslibConfig); + const environmentWithInfos: RequireKey[] = []; // User provided ids should take precedence over generated ids. const usedIds = rsbuildConfigWithLibInfo @@ -1446,6 +1453,7 @@ export async function composeRsbuildEnvironments( for (const { format, id, config } of rsbuildConfigWithLibInfo) { const libId = typeof id === 'string' ? id : composeDefaultId(format); environments[libId] = config; + environmentWithInfos.push({ id: libId, format, config }); } const conflictIds = usedIds.filter( @@ -1457,7 +1465,7 @@ export async function composeRsbuildEnvironments( ); } - return environments; + return { environments, environmentWithInfos }; } export const pruneEnvironments = ( diff --git a/packages/core/src/css/cssConfig.ts b/packages/core/src/css/cssConfig.ts index b57feac32..fd4559a55 100644 --- a/packages/core/src/css/cssConfig.ts +++ b/packages/core/src/css/cssConfig.ts @@ -2,7 +2,7 @@ import { createRequire } from 'node:module'; import path from 'node:path'; import type { CSSLoaderOptions, - RsbuildConfig, + EnvironmentConfig, RsbuildPlugin, } from '@rsbuild/core'; import { CSS_EXTENSIONS_PATTERN } from '../constant'; @@ -147,7 +147,7 @@ const pluginLibCss = (rootDir: string): RsbuildPlugin => ({ export const composeCssConfig = ( rootDir: string | null, bundle = true, -): RsbuildConfig => { +): EnvironmentConfig => { if (bundle || rootDir === null) { return {}; } diff --git a/packages/core/src/plugins/EntryChunkPlugin.ts b/packages/core/src/plugins/EntryChunkPlugin.ts index 0f183cada..c9377f74e 100644 --- a/packages/core/src/plugins/EntryChunkPlugin.ts +++ b/packages/core/src/plugins/EntryChunkPlugin.ts @@ -1,7 +1,7 @@ import { chmodSync } from 'node:fs'; import { createRequire } from 'node:module'; import { - type RsbuildConfig, + type EnvironmentConfig, type RsbuildPlugin, type Rspack, rspack, @@ -194,7 +194,7 @@ export const composeEntryChunkConfig = ({ enabledImportMetaUrlShim, }: { enabledImportMetaUrlShim: boolean; -}): RsbuildConfig => { +}): EnvironmentConfig => { return { plugins: [entryModuleLoaderRsbuildPlugin()], tools: { diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index 8a45c09ed..74f3b141e 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -1,4 +1,4 @@ -import type { RsbuildConfig, Rspack } from '@rsbuild/core'; +import type { EnvironmentConfig, RsbuildConfig, Rspack } from '@rsbuild/core'; import type { PluginDtsOptions } from 'rsbuild-plugin-dts'; import type { GetAsyncFunctionFromUnion } from './utils'; @@ -22,11 +22,11 @@ export type EcmaScriptVersion = FixedEcmaVersions | LatestEcmaVersions; export type RsbuildConfigWithLibInfo = { id?: string; format: Format; - config: RsbuildConfig; + config: EnvironmentConfig; }; export type RsbuildConfigEntry = NonNullable< - NonNullable['entry'] + NonNullable['entry'] >; export type RsbuildConfigEntryItem = RsbuildConfigEntry[string]; export type RspackResolver = GetAsyncFunctionFromUnion< @@ -34,7 +34,7 @@ export type RspackResolver = GetAsyncFunctionFromUnion< >; export type RsbuildConfigOutputTarget = NonNullable< - RsbuildConfig['output'] + EnvironmentConfig['output'] >['target']; export type Syntax = @@ -107,7 +107,7 @@ export type Redirect = { // dts?: DtsRedirect; }; -export interface LibConfig extends RsbuildConfig { +export interface LibConfig extends EnvironmentConfig { /** * The unique identifier of the library. * @defaultValue `undefined` @@ -201,7 +201,7 @@ export interface LibConfig extends RsbuildConfig { umdName?: string; } -export type LibOnlyConfig = Omit; +export type LibOnlyConfig = Omit; export interface RslibConfig extends RsbuildConfig { lib: LibConfig[]; diff --git a/packages/core/src/types/utils.ts b/packages/core/src/types/utils.ts index ac9b0bb8f..0b4209c92 100644 --- a/packages/core/src/types/utils.ts +++ b/packages/core/src/types/utils.ts @@ -11,6 +11,8 @@ export type DeepRequired = Required<{ [K in keyof T]: T[K] extends Required ? T[K] : DeepRequired; }>; +export type RequireKey = T & { [P in K]-?: T[P] }; + export type ExcludesFalse = (x: T | false | undefined | null) => x is T; export type GetAsyncFunctionFromUnion = T extends ( diff --git a/packages/core/src/utils/syntax.ts b/packages/core/src/utils/syntax.ts index 7ab9aa0c9..b07ab1236 100644 --- a/packages/core/src/utils/syntax.ts +++ b/packages/core/src/utils/syntax.ts @@ -1,4 +1,4 @@ -import type { RsbuildConfig, Rspack } from '@rsbuild/core'; +import type { EnvironmentConfig, Rspack } from '@rsbuild/core'; import type { EcmaScriptVersion, FixedEcmaVersions, @@ -191,8 +191,10 @@ export function transformSyntaxToRspackTarget( export function transformSyntaxToBrowserslist( syntax: Syntax, - target: NonNullable['target'], -): NonNullable['overrideBrowserslist']> { + target: NonNullable['target'], +): NonNullable< + NonNullable['overrideBrowserslist'] +> { const handleSyntaxItem = ( syntaxItem: EcmaScriptVersion | string, ): string[] => { diff --git a/packages/core/tests/__snapshots__/config.test.ts.snap b/packages/core/tests/__snapshots__/config.test.ts.snap index 4fe3fc0ed..b83bea934 100644 --- a/packages/core/tests/__snapshots__/config.test.ts.snap +++ b/packages/core/tests/__snapshots__/config.test.ts.snap @@ -7,7 +7,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config 1 "dev": { "progressBar": false, }, - "mode": "production", "output": { "dataUriLimit": 0, "distPath": { @@ -249,7 +248,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config 1 "dev": { "progressBar": false, }, - "mode": "production", "output": { "dataUriLimit": 0, "distPath": { @@ -483,7 +481,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config 1 "dev": { "progressBar": false, }, - "mode": "production", "output": { "distPath": { "css": "./", diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index a353b311c..ebf27a9c6 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -415,7 +415,8 @@ describe('id', () => { ], }; - const composedRsbuildConfig = await composeRsbuildEnvironments(rslibConfig); + const { environments: composedRsbuildConfig } = + await composeRsbuildEnvironments(rslibConfig); expect(Object.keys(composedRsbuildConfig)).toMatchInlineSnapshot(` [ @@ -452,7 +453,8 @@ describe('id', () => { ], }; - const composedRsbuildConfig = await composeRsbuildEnvironments(rslibConfig); + const { environments: composedRsbuildConfig } = + await composeRsbuildEnvironments(rslibConfig); expect(Object.keys(composedRsbuildConfig)).toMatchInlineSnapshot(` [ "esm1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4200510bf..2e2a68170 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -568,6 +568,8 @@ importers: tests/integration/cli/inspect: {} + tests/integration/cli/mf-dev: {} + tests/integration/copy: {} tests/integration/decorators/default: {} @@ -702,7 +704,9 @@ importers: specifier: ^1.2.0 version: 1.2.0(@rsbuild/core@1.1.12) - tests/integration/plugins: {} + tests/integration/plugins/basic: {} + + tests/integration/plugins/mf-dev: {} tests/integration/polyfill: dependencies: diff --git a/tests/integration/cli/build/build.test.ts b/tests/integration/cli/build/build.test.ts index e52d77055..c10a4ed3b 100644 --- a/tests/integration/cli/build/build.test.ts +++ b/tests/integration/cli/build/build.test.ts @@ -77,12 +77,14 @@ describe('build command', async () => { cwd: __dirname, }); - const files = await globContentJSON(path.join(__dirname, 'dist')); + const files = await globContentJSON( + path.join(__dirname, 'custom-root/dist'), + ); const fileNames = Object.keys(files).sort(); expect(fileNames).toMatchInlineSnapshot(` [ - "/tests/integration/cli/build/dist/root/index.cjs", - "/tests/integration/cli/build/dist/root/index.js", + "/tests/integration/cli/build/custom-root/dist/root/index.cjs", + "/tests/integration/cli/build/custom-root/dist/root/index.js", ] `); }); diff --git a/tests/integration/cli/build/custom-root/rslib.config.ts b/tests/integration/cli/build/custom-root/rslib.config.ts index 3285ebec0..c34299196 100644 --- a/tests/integration/cli/build/custom-root/rslib.config.ts +++ b/tests/integration/cli/build/custom-root/rslib.config.ts @@ -1,3 +1,4 @@ +import { join } from 'node:path'; import { defineConfig } from '@rslib/core'; import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; @@ -18,4 +19,9 @@ export default defineConfig({ }, }), ], + source: { + entry: { + index: join(__dirname, '../src/index.ts'), + }, + }, }); diff --git a/tests/integration/cli/mf-dev/dev.test.ts b/tests/integration/cli/mf-dev/dev.test.ts new file mode 100644 index 000000000..bc7fd79b2 --- /dev/null +++ b/tests/integration/cli/mf-dev/dev.test.ts @@ -0,0 +1,44 @@ +import { exec } from 'node:child_process'; +import { join } from 'node:path'; +import { describe } from 'node:test'; +import fse, { existsSync } from 'fs-extra'; +import { awaitFileExists } from 'test-helper'; +import { expect, test } from 'vitest'; + +const distFolder = join(__dirname, 'dist'); + +describe('mf-dev', () => { + test('mf-dev --lib', async () => { + const fixturePath = join(__dirname); + fse.removeSync(distFolder); + const distPath = join(distFolder, 'mf0', 'index.js'); + + const childProcess = exec('npx rslib mf-dev --lib mf0', { + cwd: fixturePath, + }); + + await awaitFileExists(distPath); + + expect(existsSync(distPath)).toBe(true); + childProcess.kill(); + }); + + test('mf-dev --lib multiple', async () => { + const fixturePath = join(__dirname); + fse.removeSync(distFolder); + const distPath1 = join(distFolder, 'mf1', 'index.js'); + const distPath2 = join(distFolder, 'mf2', 'index.js'); + + const childProcess = exec('npx rslib mf-dev --lib mf1 --lib mf2', { + cwd: fixturePath, + }); + + await awaitFileExists(distPath1); + await awaitFileExists(distPath2); + + expect(existsSync(distPath1)).toBe(true); + expect(existsSync(distPath2)).toBe(true); + + childProcess.kill(); + }); +}); diff --git a/tests/integration/plugins/package.json b/tests/integration/cli/mf-dev/package.json similarity index 68% rename from tests/integration/plugins/package.json rename to tests/integration/cli/mf-dev/package.json index e89a7a76b..c6b058e87 100644 --- a/tests/integration/plugins/package.json +++ b/tests/integration/cli/mf-dev/package.json @@ -1,5 +1,5 @@ { - "name": "plugins-test", + "name": "cli-mf-dev-test", "version": "1.0.0", "private": true, "type": "module" diff --git a/tests/integration/cli/mf-dev/rslib.config.ts b/tests/integration/cli/mf-dev/rslib.config.ts new file mode 100644 index 000000000..958ccfc2f --- /dev/null +++ b/tests/integration/cli/mf-dev/rslib.config.ts @@ -0,0 +1,37 @@ +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; +import { defineConfig } from '@rslib/core'; + +export default defineConfig({ + lib: [ + { + format: 'mf', + output: { + distPath: { + root: 'dist/mf0', + }, + }, + }, + { + format: 'mf', + output: { + distPath: { + root: 'dist/mf1', + }, + }, + }, + { + id: 'mf2', + format: 'mf', + output: { + distPath: { + root: 'dist/mf2', + }, + }, + }, + ], + plugins: [ + pluginModuleFederation({ + name: 'test', + }), + ], +}); diff --git a/tests/integration/cli/mf-dev/src/index.ts b/tests/integration/cli/mf-dev/src/index.ts new file mode 100644 index 000000000..3329a7d97 --- /dev/null +++ b/tests/integration/cli/mf-dev/src/index.ts @@ -0,0 +1 @@ +export const foo = 'foo'; diff --git a/tests/integration/plugins/basic/package.json b/tests/integration/plugins/basic/package.json new file mode 100644 index 000000000..64bab199e --- /dev/null +++ b/tests/integration/plugins/basic/package.json @@ -0,0 +1,6 @@ +{ + "name": "plugins-basic-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/plugins/rslib.config.ts b/tests/integration/plugins/basic/rslib.config.ts similarity index 100% rename from tests/integration/plugins/rslib.config.ts rename to tests/integration/plugins/basic/rslib.config.ts diff --git a/tests/integration/plugins/src/index.ts b/tests/integration/plugins/basic/src/index.ts similarity index 100% rename from tests/integration/plugins/src/index.ts rename to tests/integration/plugins/basic/src/index.ts diff --git a/tests/integration/plugins/index.test.ts b/tests/integration/plugins/index.test.ts index 5fac540ba..1881aaa56 100644 --- a/tests/integration/plugins/index.test.ts +++ b/tests/integration/plugins/index.test.ts @@ -1,12 +1,32 @@ +import { exec } from 'node:child_process'; import fs from 'node:fs'; -import { buildAndGetResults } from 'test-helper'; +import { join } from 'node:path'; +import { awaitFileExists, buildAndGetResults } from 'test-helper'; import { expect, test } from 'vitest'; -import { distIndex } from './rslib.config'; +import { distIndex } from './basic/rslib.config'; +import { plugin1Path, plugin2Path } from './mf-dev/rslib.config'; test('should run shared plugins only once', async () => { - const fixturePath = __dirname; + const fixturePath = join(__dirname, 'basic'); await buildAndGetResults({ fixturePath }); const content = fs.readFileSync(distIndex, 'utf-8'); expect(content).toEqual('count: 1'); }); + +test('should merge plugins correctly', async () => { + const fixturePath = join(__dirname, 'mf-dev'); + const childProcess = exec('npx rslib mf-dev', { + cwd: fixturePath, + }); + + await awaitFileExists(plugin1Path); + await awaitFileExists(plugin2Path); + + const content1 = fs.readFileSync(plugin1Path, 'utf-8'); + const content2 = fs.readFileSync(plugin2Path, 'utf-8'); + expect(content1).toEqual('plugin1 count: 1'); + expect(content2).toEqual('plugin2'); + + childProcess.kill(); +}); diff --git a/tests/integration/plugins/mf-dev/package.json b/tests/integration/plugins/mf-dev/package.json new file mode 100644 index 000000000..4a5d80789 --- /dev/null +++ b/tests/integration/plugins/mf-dev/package.json @@ -0,0 +1,6 @@ +{ + "name": "plugins-mf-dev-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/plugins/mf-dev/rslib.config.ts b/tests/integration/plugins/mf-dev/rslib.config.ts new file mode 100644 index 000000000..4d539d437 --- /dev/null +++ b/tests/integration/plugins/mf-dev/rslib.config.ts @@ -0,0 +1,37 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; +import type { RsbuildPlugin } from '@rsbuild/core'; +import { defineConfig } from '@rslib/core'; + +let count = 0; +export const plugin1Path = path.resolve(__dirname, 'dist/plugin1.txt'); +export const plugin2Path = path.resolve(__dirname, 'dist/plugin2.txt'); + +const testPlugin1: RsbuildPlugin = { + name: 'test1', + setup(api) { + api.onDevCompileDone(() => { + fs.writeFileSync(plugin1Path, `plugin1 count: ${++count}`); + }); + }, +}; + +const testPlugin2: RsbuildPlugin = { + name: 'test2', + setup(api) { + api.onDevCompileDone(() => { + fs.writeFileSync(plugin2Path, 'plugin2'); + }); + }, +}; + +export default defineConfig({ + lib: [ + { + format: 'mf', + plugins: [pluginModuleFederation({ name: 'test-plugins' }), testPlugin2], + }, + ], + plugins: [testPlugin1], +}); diff --git a/tests/integration/plugins/mf-dev/src/index.ts b/tests/integration/plugins/mf-dev/src/index.ts new file mode 100644 index 000000000..54cc258f8 --- /dev/null +++ b/tests/integration/plugins/mf-dev/src/index.ts @@ -0,0 +1 @@ +export const test = 'hello'; diff --git a/tests/integration/server/index.test.ts b/tests/integration/server/index.test.ts index 6b6d49476..7c35b6b7f 100644 --- a/tests/integration/server/index.test.ts +++ b/tests/integration/server/index.test.ts @@ -16,13 +16,13 @@ describe('server config', async () => { expect(logoExists).toBe(false); }); - test('mf dev command', async () => { + test('mf-dev command', async () => { const fixturePath = join(__dirname, 'mf-dev'); const distPath = join(fixturePath, 'dist'); const rsbuildConfigFile = join(distPath, '.rsbuild/rsbuild.config.mjs'); fse.removeSync(distPath); - const childProcess = exec('npx rslib mf dev', { + const childProcess = exec('npx rslib mf-dev', { cwd: fixturePath, env: { ...process.env, @@ -33,11 +33,8 @@ describe('server config', async () => { await awaitFileExists(rsbuildConfigFile); childProcess.kill(); - // Check if the server config is merged correctly const rsbuildConfigContent = await fse.readFile(rsbuildConfigFile, 'utf-8'); - expect(rsbuildConfigContent).toContain(`base: '/'`); expect(rsbuildConfigContent).toContain('open: true'); - expect(rsbuildConfigContent).toContain('port: 3002'); - expect(rsbuildConfigContent).toContain('printUrls: false'); + expect(rsbuildConfigContent).toContain('port: 3001'); }); }); diff --git a/tests/integration/server/mf-dev/rslib.config.ts b/tests/integration/server/mf-dev/rslib.config.ts index 3ad7e4e96..a483a3f36 100644 --- a/tests/integration/server/mf-dev/rslib.config.ts +++ b/tests/integration/server/mf-dev/rslib.config.ts @@ -5,10 +5,6 @@ export default defineConfig({ lib: [ { format: 'mf', - server: { - port: 3002, - printUrls: false, - }, }, ], server: { diff --git a/website/docs/en/config/lib/index.mdx b/website/docs/en/config/lib/index.mdx index 48fb0254a..93b6d3b80 100644 --- a/website/docs/en/config/lib/index.mdx +++ b/website/docs/en/config/lib/index.mdx @@ -3,7 +3,7 @@ - **Type:** ```ts -interface LibConfig extends RsbuildConfig { +interface LibConfig extends EnvironmentConfig { format?: Format; bundle?: boolean; autoExtension?: boolean; diff --git a/website/docs/en/guide/advanced/module-federation.mdx b/website/docs/en/guide/advanced/module-federation.mdx index 73d85f23c..5217f177b 100644 --- a/website/docs/en/guide/advanced/module-federation.mdx +++ b/website/docs/en/guide/advanced/module-federation.mdx @@ -94,14 +94,14 @@ However, if you want this Rslib Module to consume other producers at the same ti Rslib support developing Module Federation Rslib project with a host application. -#### 1. Start `rslib mf dev` command of library +#### 1. Start `rslib mf-dev` command of library Adding the `dev` command to the `package.json` file: ```json title="package.json" { "scripts": { - "dev": "rslib mf dev" + "dev": "rslib mf-dev" } } ``` @@ -152,14 +152,14 @@ Then start the host app with `rsbuild dev`. Rslib support developing Module Federation Rslib project with Storybook. -#### 1. Start `rslib mf dev` command of library +#### 1. Start `rslib mf-dev` command of library Adding the `dev` command to the `package.json` file: ```json title="package.json" { "scripts": { - "dev": "rslib mf dev" + "dev": "rslib mf-dev" } } ``` diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index 93b681d63..f587005e6 100644 --- a/website/docs/en/guide/basic/cli.mdx +++ b/website/docs/en/guide/basic/cli.mdx @@ -16,14 +16,14 @@ The output is shown below: Usage: rslib [options] Options: - -V, --version output the version number - -h, --help display help for command + -V, --version output the version number + -h, --help display help for command Commands: - build [options] build the library for production - inspect [options] inspect the Rsbuild / Rspack configs of Rslib projects - mf [options] start Rsbuild dev server of Module Federation format - help [command] display help for command + build [options] build the library for production + inspect [options] inspect the Rsbuild / Rspack configs of Rslib projects + mf-dev [options] start Rsbuild dev server of Module Federation format + help [command] display help for command ``` ## rslib build @@ -40,7 +40,7 @@ Options: -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 --env-dir specify the directory to load `.env` files - --lib build the specified library (may be repeated) + --lib specify the library (repeatable, e.g. --lib esm --lib cjs) -w --watch turn on watch mode, watch for changes and rebuild -h, --help display help for command ``` @@ -149,7 +149,7 @@ Options: -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 --env-dir specify the directory to load `.env` files - --lib inspect the specified library (may be repeated) + --lib specify the library (repeatable, e.g. --lib esm --lib cjs) --output specify inspect content output path (default: ".rsbuild") --verbose show full function definitions in output -h, --help display help for command @@ -192,14 +192,14 @@ Inspect config succeed, open following files to view the content: - Rspack Config (cjs): /project/dist/.rsbuild/rspack.config.cjs.mjs ``` -## rslib mf dev +## rslib mf-dev -The `rslib mf dev` command is utilized to start Rsbuild dev server for the [Module Federation](/guide/advanced/module-federation) format. +The `rslib mf-dev` command is utilized to start Rsbuild dev server for the [Module Federation](/guide/advanced/module-federation) format. This enables you to develop and debug your mf format module within the host app. ```text -Usage: rslib mf [options] +Usage: rslib mf-dev [options] start Rsbuild dev server of Module Federation format @@ -208,6 +208,7 @@ Options: -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 --env-dir specify the directory to load `.env` files + --lib specify the library (repeatable, e.g. --lib esm --lib cjs) -h, --help display help for command ``` diff --git a/website/docs/zh/config/lib/index.mdx b/website/docs/zh/config/lib/index.mdx index b034987cf..4b2510a75 100644 --- a/website/docs/zh/config/lib/index.mdx +++ b/website/docs/zh/config/lib/index.mdx @@ -1 +1,30 @@ # Lib 配置 + +- **类型:** + +```ts +interface LibConfig extends EnvironmentConfig { + format?: Format; + bundle?: boolean; + autoExtension?: boolean; + autoExternal?: AutoExternal; + redirect?: Redirect; + syntax?: Syntax; + externalHelpers?: boolean; + banner?: BannerAndFooter; + footer?: BannerAndFooter; + shims?: Shims; + dts?: Dts; + umdName?: string; +} + +interface RslibConfig extends RsbuildConfig { + lib: LibConfig[]; +} +``` + +- **默认值:** `undefined` + +- **必选:** `true` + +`lib` 配置是一个对象数组,每个对象代表一组不同的配置。这些配置包括所有 Rsbuild 配置以及 Rslib 特定的配置,可以生成不同格式的产物。 diff --git a/website/docs/zh/guide/advanced/module-federation.mdx b/website/docs/zh/guide/advanced/module-federation.mdx index 04a8db24f..979661411 100644 --- a/website/docs/zh/guide/advanced/module-federation.mdx +++ b/website/docs/zh/guide/advanced/module-federation.mdx @@ -94,14 +94,14 @@ export default defineConfig({ Rslib 支持宿主应用和 Rslib 模块联邦项目同时开发。 -#### 1. 启动库的 `rslib mf dev` 命令 +#### 1. 启动库的 `rslib mf-dev` 命令 添加 `dev` 命令在 `package.json` 文件: ```json title="package.json" { "scripts": { - "dev": "rslib mf dev" + "dev": "rslib mf-dev" } } ``` @@ -151,14 +151,14 @@ export default defineConfig({ Rslib 支持使用 Storybook 开发 Rslib 模块联邦项目。 -#### 1. 启动库的 `rslib mf dev` 命令 +#### 1. 启动库的 `rslib mf-dev` 命令 添加 `dev` 命令在 `package.json` 文件: ```json title="package.json" { "scripts": { - "dev": "rslib mf dev" + "dev": "rslib mf-dev" } } ``` diff --git a/website/docs/zh/guide/basic/cli.mdx b/website/docs/zh/guide/basic/cli.mdx index 34040b08a..924892157 100644 --- a/website/docs/zh/guide/basic/cli.mdx +++ b/website/docs/zh/guide/basic/cli.mdx @@ -22,7 +22,7 @@ Options: Commands: build [options] 构建用于生产环境的产物 inspect [options] 检查 Rslib 项目的 Rsbuild 配置和 Rspack 配置 - mf [options] 为 Module Federation 格式的库启用 Rsbuild 开发服务器。 + mf-dev [options] 为 Module Federation 格式的库启用 Rsbuild 开发服务器。 help [command] 显示命令帮助 ``` @@ -40,7 +40,7 @@ Options: -r --root 指定项目根目录,可以是绝对路径或者相对于 cwd 的路径 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 - --lib 仅构建指定的库(可以重复) + --lib 指定库(可重复,例如 --lib esm --lib cjs) -w --watch 开启 watch 模式, 监听文件变更并重新构建 -h, --help 显示命令帮助 ``` @@ -149,8 +149,8 @@ Options: -r --root 指定项目根目录,可以是绝对路径或者相对于 cwd 的路径 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 - --lib 检查指定的库(可以重复) - --output 指定检查内容输出路径(默认:".rsbuild") + --lib 指定库(可重复,例如 --lib esm --lib cjs) + --output 指定检查内容输出路径(默认:".rsbuild") --verbose 在输出中显示完整的函数定义 -h, --help 显示命令帮助 ``` @@ -192,14 +192,14 @@ Inspect config succeed, open following files to view the content: - Rspack Config (cjs): /project/dist/.rsbuild/rspack.config.cjs.mjs ``` -## rslib mf dev +## rslib mf-dev -`rslib mf dev` 命令用于为 [Module Federation](/guide/advanced/module-federation) 格式的库启用 Rsbuild 开发服务器。 +`rslib mf-dev` 命令用于为 [Module Federation](/guide/advanced/module-federation) 格式的库启用 Rsbuild 开发服务器。 这允许你在 host 应用中访问和调试 mf 格式的模块。 ```text -Usage: rslib mf [options] +Usage: rslib mf-dev [options] 为 Module Federation 格式的库启用 Rsbuild 开发服务器。 @@ -208,6 +208,7 @@ Options: -r --root 指定项目根目录,可以是绝对路径或者相对于 cwd 的路径 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 + --lib 指定库(可重复,例如 --lib esm --lib cjs) -h, --help 显示命令帮助 ```