diff --git a/e2e/cases/cli/config-loader/index.test.ts b/e2e/cases/cli/config-loader/index.test.ts new file mode 100644 index 0000000000..bc039e20b9 --- /dev/null +++ b/e2e/cases/cli/config-loader/index.test.ts @@ -0,0 +1,24 @@ +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import { globContentJSON, rspackOnlyTest } from '@e2e/helper'; +import { expect, test } from '@playwright/test'; + +rspackOnlyTest('should use Node.js native loader to load config', async () => { + // TODO: fix this test on Windows + if (process.platform === 'win32') { + test.skip(); + } + + execSync('npx rsbuild build --config-loader native', { + cwd: __dirname, + env: { + ...process.env, + NODE_OPTIONS: '--experimental-strip-types', + }, + }); + + const outputs = await globContentJSON(path.join(__dirname, 'dist-custom')); + const outputFiles = Object.keys(outputs); + + expect(outputFiles.length > 1).toBeTruthy(); +}); diff --git a/e2e/cases/cli/config-loader/rsbuild.config.mts b/e2e/cases/cli/config-loader/rsbuild.config.mts new file mode 100644 index 0000000000..cae7335a55 --- /dev/null +++ b/e2e/cases/cli/config-loader/rsbuild.config.mts @@ -0,0 +1,9 @@ +import type { RsbuildConfig } from '@rsbuild/core'; + +export default { + output: { + distPath: { + root: 'dist-custom', + }, + }, +} satisfies RsbuildConfig; diff --git a/e2e/cases/cli/config-loader/src/index.js b/e2e/cases/cli/config-loader/src/index.js new file mode 100644 index 0000000000..ddc67c9b01 --- /dev/null +++ b/e2e/cases/cli/config-loader/src/index.js @@ -0,0 +1 @@ +console.log('hello!'); diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 7496353ef1..df68472121 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -1,4 +1,5 @@ import cac, { type CAC, type Command } from 'cac'; +import type { ConfigLoader } from '../config'; import { logger } from '../logger'; import { onBeforeRestartServer } from '../server/restart'; import type { RsbuildMode } from '../types'; @@ -8,6 +9,7 @@ export type CommonOptions = { root?: string; mode?: RsbuildMode; config?: string; + configLoader?: ConfigLoader; envDir?: string; envMode?: string; open?: boolean | string; @@ -36,6 +38,13 @@ const applyCommonOptions = (cli: CAC) => { '-c, --config ', 'specify the configuration file, can be a relative or absolute path', ) + .option( + '--config-loader ', + 'specify the loader to load the config file, can be `jiti` or `native`', + { + default: 'jiti', + }, + ) .option( '-r, --root ', 'specify the project root directory, can be an absolute path or a path relative to cwd', diff --git a/packages/core/src/cli/init.ts b/packages/core/src/cli/init.ts index 71b7da68c5..1ec59ff00b 100644 --- a/packages/core/src/cli/init.ts +++ b/packages/core/src/cli/init.ts @@ -41,6 +41,7 @@ export async function init({ cwd: root, path: commonOpts.config, envMode: commonOpts.envMode, + loader: commonOpts.configLoader, }); const command = process.argv[2]; diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 1e16e7ae75..da9412c302 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -425,6 +425,13 @@ export type LoadConfigOptions = { * @default process.env.NODE_ENV */ envMode?: string; + /** + * Specify the config loader, can be `jiti` or `native`. + * - 'jiti': Use `jiti` as loader, which supports TypeScript and ESM out of the box + * - 'native': Use native Node.js loader, requires TypeScript support in Node.js >= 22.6 + * @default 'jiti' + */ + loader?: ConfigLoader; }; export type LoadConfigResult = { @@ -439,11 +446,14 @@ export type LoadConfigResult = { filePath: string | null; }; +export type ConfigLoader = 'jiti' | 'native'; + export async function loadConfig({ cwd = process.cwd(), path, envMode, meta, + loader = 'jiti', }: LoadConfigOptions = {}): Promise { const configFilePath = resolveConfigPath(cwd, path); @@ -461,11 +471,17 @@ export async function loadConfig({ let configExport: RsbuildConfigExport; - if (/\.(?:js|mjs|cjs)$/.test(configFilePath)) { + if (/\.(?:js|mjs|cjs)$/.test(configFilePath) || loader === 'native') { try { const exportModule = await import(`${configFilePath}?t=${Date.now()}`); configExport = exportModule.default ? exportModule.default : exportModule; } catch (err) { + if (loader === 'native') { + logger.error( + `Failed to load file with native loader: ${color.dim(configFilePath)}`, + ); + throw err; + } logger.debug( `Failed to load file with dynamic import: ${color.dim(configFilePath)}`, ); diff --git a/website/docs/en/api/javascript-api/core.mdx b/website/docs/en/api/javascript-api/core.mdx index fdf38d39c2..9ff1881d54 100644 --- a/website/docs/en/api/javascript-api/core.mdx +++ b/website/docs/en/api/javascript-api/core.mdx @@ -56,6 +56,7 @@ function loadConfig(params?: { path?: string; meta?: Record; envMode?: string; + loader?: 'jiti' | 'native'; }): Promise<{ content: RsbuildConfig; filePath: string | null; diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index a6ed70f82e..809ffaf11f 100644 --- a/website/docs/en/guide/basic/cli.mdx +++ b/website/docs/en/guide/basic/cli.mdx @@ -27,15 +27,16 @@ Commands: Rsbuild CLI provides several common flags that can be used with all commands: -| Flag | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, see [Specify config file](/guide/basic/configure-rsbuild#specify-config-file) | -| `-r, --root ` | Specify the project root directory | -| `-m, --mode ` | Specify the build mode (`development`, `production` or `none`), see [mode](/config/mode) | -| `--env-mode ` | Specify the env mode to load the `.env.[mode]` file, see [Env mode](/guide/advanced/env-vars#env-mode) | -| `--env-dir ` | Specify the directory to load `.env` files, see [Env directory](/guide/advanced/env-vars#env-directory) | -| `--environment ` | Specify the name of environment to build, see [Build specified environment](/guide/advanced/environments#build-specified-environment) | -| `-h, --help` | Display help for command | +| Flag | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, see [Specify config file](/guide/basic/configure-rsbuild#specify-config-file) | +| `--config-loader ` | Specify the config loader, can be `jiti` or `native`, see [Specify config loader](/guide/basic/configure-rsbuild#specify-config-loader) | +| `-r, --root ` | Specify the project root directory | +| `-m, --mode ` | Specify the build mode (`development`, `production` or `none`), see [mode](/config/mode) | +| `--env-mode ` | Specify the env mode to load the `.env.[mode]` file, see [Env mode](/guide/advanced/env-vars#env-mode) | +| `--env-dir ` | Specify the directory to load `.env` files, see [Env directory](/guide/advanced/env-vars#env-directory) | +| `--environment ` | Specify the name of environment to build, see [Build specified environment](/guide/advanced/environments#build-specified-environment) | +| `-h, --help` | Display help for command | ## rsbuild dev diff --git a/website/docs/en/guide/basic/configure-rsbuild.mdx b/website/docs/en/guide/basic/configure-rsbuild.mdx index b532e63123..eed57fa0f3 100644 --- a/website/docs/en/guide/basic/configure-rsbuild.mdx +++ b/website/docs/en/guide/basic/configure-rsbuild.mdx @@ -93,10 +93,6 @@ export default defineConfig({ }); ``` -:::tip -When you use the `.ts`, `.mts`, and `.cts` extensions, Rsbuild will use [jiti](https://github.com/unjs/jiti) to load configuration files, providing interoperability between ESM and CommonJS. The behavior of module resolution differs slightly from the native behavior of Node.js. -::: - ## Specify config file Rsbuild CLI uses the `--config` option to specify the config file, which can be set to a relative path or an absolute path. @@ -117,6 +113,26 @@ You can also abbreviate the `--config` option to `-c`: rsbuild build -c rsbuild.prod.config.mjs ``` +## Specify config loader + +When you use a configuration file with the `.ts`, `.mts`, and `.cts` extensions, Rsbuild will use [jiti](https://github.com/unjs/jiti) to load configuration files, providing interoperability between ESM and CommonJS. The behavior of module resolution differs slightly from the native behavior of Node.js. + +If your JavaScript runtime already natively supports TypeScript, you can use the `--config-loader native` option to use the Node.js native loader to load the configuration file. This can ensure that the module resolution behavior is consistent with the native behavior of Node.js and has better performance. + +For example, Node.js v22.6.0+ already natively supports TypeScript, you can use the following command to use the Node.js native loader to load the configuration file: + +```bash +# Node.js v23.6.0+ +# No need to set --experimental-strip-types +npx rsbuild build --config-loader native + +# Node.js v22.6.0 - v23.5.0 +# Need to set --experimental-strip-types +NODE_OPTIONS="--experimental-strip-types" npx rsbuild build --config-loader native +``` + +> See [Node.js - Running TypeScript Natively](https://nodejs.org/en/learn/typescript/run-natively#running-typescript-natively) for more details. + ## Using environment variables In the configuration file, you can use Node.js environment variables such as `process.env.NODE_ENV` to dynamically set different configurations: diff --git a/website/docs/zh/api/javascript-api/core.mdx b/website/docs/zh/api/javascript-api/core.mdx index c9bc6c0db8..754ec2947b 100644 --- a/website/docs/zh/api/javascript-api/core.mdx +++ b/website/docs/zh/api/javascript-api/core.mdx @@ -56,6 +56,7 @@ function loadConfig(params?: { path?: string; meta?: Record; envMode?: string; + loader?: 'jiti' | 'native'; }): Promise<{ content: RsbuildConfig; filePath: string | null; diff --git a/website/docs/zh/guide/basic/cli.mdx b/website/docs/zh/guide/basic/cli.mdx index 83120f80d3..27ad74e6af 100644 --- a/website/docs/zh/guide/basic/cli.mdx +++ b/website/docs/zh/guide/basic/cli.mdx @@ -27,15 +27,16 @@ Commands: Rsbuild CLI 提供了一些公共选项,可以用于所有命令: -| 选项 | 描述 | -| ----------------------- | ------------------------------------------------------------------------------------------------------------ | -| `-c, --config ` | 指定配置文件路径,可以为相对路径或绝对路径,详见 [指定配置文件](/guide/basic/configure-rsbuild#指定配置文件) | -| `-r, --root ` | 指定项目根目录,可以是绝对路径或者相对于 cwd 的路径 | -| `-m, --mode ` | 指定构建模式,可以是 `development`,`production` 或 `none`,详见 [mode](/config/mode) | -| `--env-mode ` | 指定 env 模式来加载 `.env.[mode]` 文件,详见 [Env 模式](/guide/advanced/env-vars#env-模式) | -| `--env-dir ` | 指定目录来加载 `.env` 文件,详见 [Env 目录](/guide/advanced/env-vars#env-目录) | -| `--environment ` | 指定需要构建的 environment 名称,详见 [构建指定环境](/guide/advanced/environments#构建指定环境) | -| `-h, --help` | 显示命令帮助 | +| 选项 | 描述 | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | 指定配置文件路径,可以为相对路径或绝对路径,详见 [指定配置文件](/guide/basic/configure-rsbuild#指定配置文件) | +| `--config-loader ` | 指定配置文件加载方式,可以为 `jiti` 或 `native`,详见 [指定加载方式](/guide/basic/configure-rsbuild#指定加载方式) | +| `-r, --root ` | 指定项目根目录,可以是绝对路径或者相对于 cwd 的路径 | +| `-m, --mode ` | 指定构建模式,可以是 `development`,`production` 或 `none`,详见 [mode](/config/mode) | +| `--env-mode ` | 指定 env 模式来加载 `.env.[mode]` 文件,详见 [Env 模式](/guide/advanced/env-vars#env-模式) | +| `--env-dir ` | 指定目录来加载 `.env` 文件,详见 [Env 目录](/guide/advanced/env-vars#env-目录) | +| `--environment ` | 指定需要构建的 environment 名称,详见 [构建指定环境](/guide/advanced/environments#构建指定环境) | +| `-h, --help` | 显示命令帮助 | ## rsbuild dev diff --git a/website/docs/zh/guide/basic/configure-rsbuild.mdx b/website/docs/zh/guide/basic/configure-rsbuild.mdx index d77548bc68..49a87ce843 100644 --- a/website/docs/zh/guide/basic/configure-rsbuild.mdx +++ b/website/docs/zh/guide/basic/configure-rsbuild.mdx @@ -93,10 +93,6 @@ export default defineConfig({ }); ``` -:::tip -当你使用 `.ts`, `.mts` 和 `.cts` 后缀时,Rsbuild 会使用 [jiti](https://github.com/unjs/jiti) 来加载配置文件,提供 ESM 与 CommonJS 的互操作性,模块解析的行为与 Node.js 原生行为存在一定差异。 -::: - ## 指定配置文件 Rsbuild CLI 通过 `--config` 选项来指定配置文件,可以设置为相对路径或绝对路径。 @@ -117,6 +113,26 @@ Rsbuild CLI 通过 `--config` 选项来指定配置文件,可以设置为相 rsbuild build -c rsbuild.prod.config.mjs ``` +## 指定加载方式 + +当你使用 `.ts`, `.mts` 和 `.cts` 后缀的配置文件时,Rsbuild 会使用 [jiti](https://github.com/unjs/jiti) 来加载配置文件,提供 ESM 与 CommonJS 的互操作性,模块解析的行为与 Node.js 原生行为存在一定差异。 + +如果你使用的 JavaScript 运行时已经原生支持 TypeScript,可以使用 `--config-loader native` 选项来使用 Node.js 原生 loader 来加载配置文件。这可以保证模块解析的行为与 Node.js 原生行为一致,并且性能更好。 + +例如,Node.js 从 v22.6.0 开始已经原生支持 TypeScript,你可以如下命令来使用 Node.js 原生 loader 来加载配置文件: + +```bash +# Node.js v23.6.0+ +# 不需要设置 --experimental-strip-types +npx rsbuild build --config-loader native + +# Node.js v22.6.0 - v23.5.0 +# 需要设置 --experimental-strip-types +NODE_OPTIONS="--experimental-strip-types" npx rsbuild build --config-loader native +``` + +> 详见 [Node.js - Running TypeScript Natively](https://nodejs.org/en/learn/typescript/run-natively#running-typescript-natively)。 + ## 使用环境变量 在配置文件中,你可以使用 `process.env.NODE_ENV` 等 Node.js 环境变量,来动态写入不同的配置: