diff --git a/e2e/cases/cli/specified-environment/index.test.ts b/e2e/cases/cli/specified-environment/index.test.ts new file mode 100644 index 0000000000..7a302ea46f --- /dev/null +++ b/e2e/cases/cli/specified-environment/index.test.ts @@ -0,0 +1,20 @@ +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import { globContentJSON } from '@e2e/helper'; +import { expect, test } from '@playwright/test'; + +test('should only build specified environment when using --environment option', async () => { + execSync('npx rsbuild build --environment web2', { + cwd: __dirname, + }); + + const files = await globContentJSON(path.join(__dirname, 'dist')); + const outputFiles = Object.keys(files); + + expect( + outputFiles.find((item) => item.includes('web1/index.html')), + ).toBeFalsy(); + expect( + outputFiles.find((item) => item.includes('web2/index.html')), + ).toBeTruthy(); +}); diff --git a/e2e/cases/cli/specified-environment/rsbuild.config.mjs b/e2e/cases/cli/specified-environment/rsbuild.config.mjs new file mode 100644 index 0000000000..8cb3a3eda0 --- /dev/null +++ b/e2e/cases/cli/specified-environment/rsbuild.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig } from '@rsbuild/core'; + +export default defineConfig({ + environments: { + web1: { + output: { + distPath: { + root: 'dist/web1', + }, + }, + }, + web2: { + output: { + distPath: { + root: 'dist/web2', + }, + }, + }, + }, +}); diff --git a/e2e/cases/cli/specified-environment/src/index.js b/e2e/cases/cli/specified-environment/src/index.js new file mode 100644 index 0000000000..ddc67c9b01 --- /dev/null +++ b/e2e/cases/cli/specified-environment/src/index.js @@ -0,0 +1 @@ +console.log('hello!'); diff --git a/packages/compat/webpack/src/initConfigs.ts b/packages/compat/webpack/src/initConfigs.ts index 2cfaaef2d7..b4a32c4062 100644 --- a/packages/compat/webpack/src/initConfigs.ts +++ b/packages/compat/webpack/src/initConfigs.ts @@ -1,7 +1,7 @@ import { - type CreateRsbuildOptions, type InspectConfigOptions, type PluginManager, + type ResolvedCreateRsbuildOptions, logger, } from '@rsbuild/core'; import { inspectConfig } from './inspectConfig'; @@ -12,7 +12,7 @@ import { generateWebpackConfig } from './webpackConfig'; export type InitConfigsOptions = { context: InternalContext; pluginManager: PluginManager; - rsbuildOptions: Required; + rsbuildOptions: ResolvedCreateRsbuildOptions; }; export async function initConfigs({ diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index b4222aea2e..3f5932fac8 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -13,6 +13,7 @@ export type CommonOptions = { open?: boolean | string; host?: string; port?: number; + environment?: string[]; }; export type BuildOptions = CommonOptions & { @@ -39,6 +40,11 @@ const applyCommonOptions = (command: Command) => { '--env-mode ', 'specify the env mode to load the `.env.[mode]` file', ) + .option( + '--environment ', + 'specify the name of environment to build', + (str, prev) => (prev ? prev.concat(str.split(',')) : str.split(',')), + ) .option('--env-dir ', 'specify the directory to load `.env` files'); }; diff --git a/packages/core/src/cli/init.ts b/packages/core/src/cli/init.ts index 7ea975e33b..a4551b6589 100644 --- a/packages/core/src/cli/init.ts +++ b/packages/core/src/cli/init.ts @@ -80,6 +80,7 @@ export async function init({ return createRsbuild({ cwd: root, rsbuildConfig: config, + environment: commonOpts.environment, }); } catch (err) { if (isRestart) { diff --git a/packages/core/src/createContext.ts b/packages/core/src/createContext.ts index a99c406ed1..0d9b579614 100644 --- a/packages/core/src/createContext.ts +++ b/packages/core/src/createContext.ts @@ -8,11 +8,11 @@ import { getHTMLPathByEntry } from './initPlugins'; import { logger } from './logger'; import type { BundlerType, - CreateRsbuildOptions, EnvironmentContext, InternalContext, NormalizedConfig, NormalizedEnvironmentConfig, + ResolvedCreateRsbuildOptions, RsbuildConfig, RsbuildContext, RsbuildEntry, @@ -35,7 +35,7 @@ function getAbsoluteDistPath( * Create context by config. */ async function createContextByConfig( - options: Required, + options: ResolvedCreateRsbuildOptions, bundlerType: BundlerType, ): Promise { const { cwd } = options; @@ -210,7 +210,7 @@ export function createPublicContext( * which can have a lot of overhead and take some side effects. */ export async function createContext( - options: Required, + options: ResolvedCreateRsbuildOptions, userRsbuildConfig: RsbuildConfig, bundlerType: BundlerType, ): Promise { @@ -223,5 +223,6 @@ export async function createContext( hooks: initHooks(), config: { ...rsbuildConfig }, originalConfig: userRsbuildConfig, + specifiedEnvironments: options.environment, }; } diff --git a/packages/core/src/createRsbuild.ts b/packages/core/src/createRsbuild.ts index dd948f2e3c..fee5e5541d 100644 --- a/packages/core/src/createRsbuild.ts +++ b/packages/core/src/createRsbuild.ts @@ -10,6 +10,7 @@ import type { InternalContext, PluginManager, PreviewServerOptions, + ResolvedCreateRsbuildOptions, RsbuildInstance, RsbuildProvider, } from './types'; @@ -99,7 +100,7 @@ export async function createRsbuild( ): Promise { const { rsbuildConfig = {} } = options; - const rsbuildOptions: Required = { + const rsbuildOptions: ResolvedCreateRsbuildOptions = { cwd: process.cwd(), rsbuildConfig, ...options, @@ -180,7 +181,10 @@ export async function createRsbuild( if (rsbuildConfig.environments) { await Promise.all( Object.entries(rsbuildConfig.environments).map(async ([name, config]) => { - if (config.plugins) { + const isEnvironmentEnabled = + !rsbuildOptions.environment || + rsbuildOptions.environment.includes(name); + if (config.plugins && isEnvironmentEnabled) { const plugins = await Promise.all(config.plugins); rsbuild.addPlugins(plugins, { environment: name, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4d429d55b3..3f5b0532a3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -40,6 +40,7 @@ export type { CreateCompiler, CreateCompilerOptions, CreateRsbuildOptions, + ResolvedCreateRsbuildOptions, CrossOrigin, CSSLoaderOptions, CSSModules, diff --git a/packages/core/src/provider/initConfigs.ts b/packages/core/src/provider/initConfigs.ts index 617f9d16f4..5e87623813 100644 --- a/packages/core/src/provider/initConfigs.ts +++ b/packages/core/src/provider/initConfigs.ts @@ -1,3 +1,4 @@ +import color from 'picocolors'; import { getDefaultEntry, normalizeConfig } from '../config'; import { JS_DIST_DIR } from '../constants'; import { @@ -9,7 +10,6 @@ import { isDebug, logger } from '../logger'; import { mergeRsbuildConfig } from '../mergeConfig'; import { initPlugins } from '../pluginManager'; import type { - CreateRsbuildOptions, InspectConfigOptions, InternalContext, MergedEnvironmentConfig, @@ -17,6 +17,7 @@ import type { NormalizedConfig, NormalizedEnvironmentConfig, PluginManager, + ResolvedCreateRsbuildOptions, RsbuildEntry, RspackConfig, } from '../types'; @@ -62,12 +63,13 @@ async function modifyEnvironmentConfig( export type InitConfigsOptions = { context: InternalContext; pluginManager: PluginManager; - rsbuildOptions: Required; + rsbuildOptions: ResolvedCreateRsbuildOptions; }; const initEnvironmentConfigs = ( normalizedConfig: NormalizedConfig, rootPath: string, + specifiedEnvironments?: string[], ): Record => { let defaultEntry: RsbuildEntry; const getDefaultEntryWithMemo = () => { @@ -80,6 +82,9 @@ const initEnvironmentConfigs = ( normalizedConfig; const { assetPrefix, lazyCompilation } = dev; + const isEnvironmentEnabled = (name: string) => + !specifiedEnvironments || specifiedEnvironments.includes(name); + const applyEnvironmentDefaultConfig = (config: MergedEnvironmentConfig) => { if (!config.source.entry) { config.source.entry = getDefaultEntryWithMemo(); @@ -94,35 +99,51 @@ const initEnvironmentConfigs = ( }; if (environments && Object.keys(environments).length) { - return Object.fromEntries( - Object.entries(environments).map(([name, config]) => { - const environmentConfig: MergedEnvironmentConfig = { - ...(mergeRsbuildConfig( - { - ...rsbuildSharedConfig, - dev: { - assetPrefix, - lazyCompilation, - }, - } as unknown as MergedEnvironmentConfig, - config as unknown as MergedEnvironmentConfig, - ) as unknown as MergedEnvironmentConfig), - }; - - return [name, applyEnvironmentDefaultConfig(environmentConfig)]; - }), + const resolvedEnvironments = Object.fromEntries( + Object.entries(environments) + .filter(([name]) => isEnvironmentEnabled(name)) + .map(([name, config]) => { + const environmentConfig: MergedEnvironmentConfig = { + ...(mergeRsbuildConfig( + { + ...rsbuildSharedConfig, + dev: { + assetPrefix, + lazyCompilation, + }, + } as unknown as MergedEnvironmentConfig, + config as unknown as MergedEnvironmentConfig, + ) as unknown as MergedEnvironmentConfig), + }; + + return [name, applyEnvironmentDefaultConfig(environmentConfig)]; + }), + ); + + if (!Object.keys(resolvedEnvironments).length) { + throw new Error( + `The current build is specified to run only in the ${color.yellow(specifiedEnvironments?.join(','))} environment, but the configuration of the specified environment was not found.`, + ); + } + return resolvedEnvironments; + } + + const defaultEnvironmentName = camelCase(rsbuildSharedConfig.output.target); + + if (!isEnvironmentEnabled(defaultEnvironmentName)) { + throw new Error( + `The current build is specified to run only in the ${color.yellow(specifiedEnvironments?.join(','))} environment, but the configuration of the specified environment was not found.`, ); } return { - [camelCase(rsbuildSharedConfig.output.target)]: - applyEnvironmentDefaultConfig({ - ...rsbuildSharedConfig, - dev: { - assetPrefix, - lazyCompilation, - }, - } as MergedEnvironmentConfig), + [defaultEnvironmentName]: applyEnvironmentDefaultConfig({ + ...rsbuildSharedConfig, + dev: { + assetPrefix, + lazyCompilation, + }, + } as MergedEnvironmentConfig), }; }; @@ -151,6 +172,7 @@ export async function initRsbuildConfig({ const mergedEnvironments = initEnvironmentConfigs( normalizeBaseConfig, context.rootPath, + context.specifiedEnvironments, ); const { diff --git a/packages/core/src/types/context.ts b/packages/core/src/types/context.ts index b2da229d07..05752ad3df 100644 --- a/packages/core/src/types/context.ts +++ b/packages/core/src/types/context.ts @@ -42,4 +42,6 @@ export type InternalContext = RsbuildContext & { getPluginAPI?: (environment?: string) => RsbuildPluginAPI; /** The environment context. */ environments: Record; + /** Only build specified environment. */ + specifiedEnvironments?: string[]; }; diff --git a/packages/core/src/types/rsbuild.ts b/packages/core/src/types/rsbuild.ts index a97f68a669..5b18ada386 100644 --- a/packages/core/src/types/rsbuild.ts +++ b/packages/core/src/types/rsbuild.ts @@ -71,8 +71,13 @@ export type CreateRsbuildOptions = { cwd?: string; /** Configurations of Rsbuild. */ rsbuildConfig?: RsbuildConfig; + /** Only build specified environment. */ + environment?: string[]; }; +export type ResolvedCreateRsbuildOptions = CreateRsbuildOptions & + Required>; + export type ProviderInstance = { readonly bundler: Bundler; @@ -104,7 +109,7 @@ export type RsbuildProvider = (options: { context: InternalContext; pluginManager: PluginManager; - rsbuildOptions: Required; + rsbuildOptions: ResolvedCreateRsbuildOptions; setCssExtractPlugin: (plugin: unknown) => void; }) => Promise>; diff --git a/packages/core/tests/environments.test.ts b/packages/core/tests/environments.test.ts index cbbb407f87..7107bafc32 100644 --- a/packages/core/tests/environments.test.ts +++ b/packages/core/tests/environments.test.ts @@ -201,6 +201,42 @@ describe('environment config', () => { ).toMatchSnapshot(); }); + it('should support run specified environment', async () => { + process.env.NODE_ENV = 'development'; + + const pluginLogs: string[] = []; + + const plugin: (pluginId: string) => RsbuildPlugin = (pluginId) => ({ + name: 'test-environment', + setup: () => { + pluginLogs.push(`run plugin in ${pluginId}`); + }, + }); + + const rsbuild = await createRsbuild({ + rsbuildConfig: { + environments: { + web: { + plugins: [plugin('web')], + }, + ssr: { + plugins: [plugin('ssr')], + }, + }, + }, + environment: ['ssr'], + }); + + rsbuild.addPlugins([plugin('global')]); + + const { + origin: { environmentConfigs }, + } = await rsbuild.inspectConfig(); + + expect(Object.keys(environmentConfigs)).toEqual(['ssr']); + expect(pluginLogs).toEqual(['run plugin in ssr', 'run plugin in global']); + }); + it('should normalize environment config correctly', async () => { process.env.NODE_ENV = 'development'; const rsbuild = await createRsbuild({ diff --git a/scripts/test-helper/src/rsbuild.ts b/scripts/test-helper/src/rsbuild.ts index cc55e96c22..6331fb33c7 100644 --- a/scripts/test-helper/src/rsbuild.ts +++ b/scripts/test-helper/src/rsbuild.ts @@ -37,7 +37,7 @@ export async function createStubRsbuild({ } > { const { createRsbuild } = await import('@rsbuild/core'); - const rsbuildOptions: Required = { + const rsbuildOptions = { cwd: process.env.REBUILD_TEST_SUITE_CWD || process.cwd(), rsbuildConfig, ...options, diff --git a/website/docs/en/guide/advanced/environments.mdx b/website/docs/en/guide/advanced/environments.mdx index 242abbc21b..338bb8e4db 100644 --- a/website/docs/en/guide/advanced/environments.mdx +++ b/website/docs/en/guide/advanced/environments.mdx @@ -100,6 +100,24 @@ export default { }; ``` +## Specify environment build + +By default, Rsbuild will build all environments in the Rsbuild configuration when you execute `rsbuild dev` or `rsbuild build`. You can build only the specified environment via `--environment `. + +```bash +# Build for all environments by default +rsbuild dev + +# Build for the web environment +rsbuild dev --environment web + +# Build for the web and ssr environments +rsbuild dev --environment web --environment node + +# Build multiple environments can be shortened to: +rsbuild dev --environment web,node +``` + ## Add plugins for specified environment Plugins configured through the [plugins](/config/plugins) field support running in all environments. If you want a plugin to run only in a specified environment, you can configure the plugin in the specified `environment`. diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index 40c4896b5e..a9ea529d40 100644 --- a/website/docs/en/guide/basic/cli.mdx +++ b/website/docs/en/guide/basic/cli.mdx @@ -39,6 +39,7 @@ Options: --port specify a port number for Rsbuild Server to listen --host specify the host that the Rsbuild Server listens to -c --config specify the configuration file, can be a relative or absolute path + --environment specify the name of environment to build --env-mode specify the env mode to load the `.env.[mode]` file --env-dir specify the directory to load `.env` files -h, --help display help for command @@ -74,6 +75,7 @@ Usage: rsbuild build [options] Options: -w --watch turn on watch mode, watch for changes and rebuild -c --config specify the configuration file, can be a relative or absolute path + --environment specify the name of environment to build --env-mode specify the env mode to load the `.env.[mode]` file --env-dir specify the directory to load `.env` files -h, --help display help for command @@ -91,6 +93,7 @@ Options: --port specify a port number for Rsbuild Server to listen --host specify the host that the Rsbuild Server listens to -c --config specify the configuration file, can be a relative or absolute path + --environment specify the name of environment to build --env-mode specify the env mode to load the `.env.[mode]` file --env-dir specify the directory to load `.env` files -h, --help display help for command @@ -112,6 +115,7 @@ Options: --output Specify the path to output in the dist (default: "/") --verbose Show the full function in the result -c --config specify the configuration file, can be a relative or absolute path + --environment specify the name of environment to build --env-mode specify the env mode to load the `.env.[mode]` file --env-dir specify the directory to load `.env` files -h, --help show command help @@ -131,7 +135,7 @@ Inspect config succeed, open following files to view the content: - Rspack Config (web): /project/dist/rspack.config.web.mjs ``` -### Specifying Environment +### Specifying Environment Mode By default, the inspect command outputs the configuration for the development environment. You can add the `--env production` option to output the configuration for the production build: diff --git a/website/docs/zh/guide/advanced/environments.mdx b/website/docs/zh/guide/advanced/environments.mdx index f77eec3e3f..61b70a1477 100644 --- a/website/docs/zh/guide/advanced/environments.mdx +++ b/website/docs/zh/guide/advanced/environments.mdx @@ -100,6 +100,24 @@ export default { }; ``` +## 仅构建指定环境 + +默认情况下,当你执行 `rsbuild dev` 或 `rsbuild build` 时,Rsbuild 会构建所有 Rsbuild 配置中的环境。你可以通过 `--environment ` 仅构建指定环境。 + +```bash +# 构建所有环境 +rsbuild dev + +# 仅构建 web 环境 +rsbuild dev --environment web + +# 构建 web 和 ssr 环境 +rsbuild dev --environment web --environment node + +# 构建多个环境可以简写为: +rsbuild dev --environment web,node +``` + ## 为指定环境添加插件 通过 [plugins](/config/plugins) 字段配置的插件支持在所有环境下运行,如果你希望某个插件仅在指定环境下运行时,将该插件配置在特定 `environment` 下即可。 diff --git a/website/docs/zh/guide/basic/cli.mdx b/website/docs/zh/guide/basic/cli.mdx index aef61ee341..7c3624569b 100644 --- a/website/docs/zh/guide/basic/cli.mdx +++ b/website/docs/zh/guide/basic/cli.mdx @@ -39,6 +39,7 @@ Options: --port 设置 Rsbuild Server 监听的端口号 --host 指定 Rsbuild Server 启动时监听的 host -c --config 指定配置文件路径,可以为相对路径或绝对路径 + --environment 指定需要构建的 environment 名称 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 -h, --help 显示命令帮助 @@ -74,6 +75,7 @@ Usage: rsbuild build [options] Options: -w --watch 开启 watch 模式, 监听文件变更并重新构建 -c --config 指定配置文件路径,可以为相对路径或绝对路径 + --environment 指定需要构建的 environment 名称 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 -h, --help 显示命令帮助 @@ -91,6 +93,7 @@ Options: --port 设置 Rsbuild Server 监听的端口号 --host 指定 Rsbuild Server 启动时监听的 host -c --config 指定配置文件路径,可以为相对路径或绝对路径 + --environment 指定需要构建的 environment 名称 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 -h, --help 显示命令帮助 @@ -112,6 +115,7 @@ Options: --output 指定在 dist 目录下输出的路径 (default: "/") --verbose 在结果中展示函数的完整内容 -c --config 指定配置文件路径,可以为相对路径或绝对路径 + --environment 指定需要构建的 environment 名称 --env-mode 指定 env 模式来加载 `.env.[mode]` 文件 --env-dir 指定目录来加载 `.env` 文件 -h, --help 显示命令帮助 @@ -131,7 +135,7 @@ Inspect config succeed, open following files to view the content: - Rspack Config (web): /project/dist/rspack.config.web.mjs ``` -### 指定环境 +### 指定环境模式 默认情况下,inspect 命令会输出开发环境的配置,你可以添加 `--env production` 选项来输出生产环境的配置: