For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /guide/configuration.md.
close
  • English
  • Configuration

    Rstack CLI centralizes the configuration for your project's tools in a single file. Define only the configurations your project needs with the define.*() APIs.

    Configuration file

    Create rstack.config.ts in the project root and call the relevant define.*() APIs:

    rstack.config.ts
    // Configuration guide: https://rstack.rs/config
    import { define } from 'rstack';
    
    define.app({
      // Rsbuild configuration
    });
    
    define.test({
      // Rstest configuration
    });
    
    define.lint({
      // Rslint configuration
    });
    
    define.fmt({
      // Formatting configuration
    });

    The configuration file does not require a default export. Each define.*() API can be called at most once; defining the same configuration type more than once throws an error.

    By default, Rstack CLI looks for a file with one of the following names:

    • rstack.config.ts
    • rstack.config.js
    • rstack.config.mts
    • rstack.config.mjs

    All rs commands accept the global -c, --config option for loading a file with a different name or location:

    rs build --config ./configs/rstack.config.ts

    Importing dependencies

    Rstack keeps a project's build, test, lint, formatting, and other settings in one rstack.config.* file. When a command loads the config file, it also loads every top-level import, even if it does not use the related configuration. Importing every tool and plugin at the top level can therefore add startup overhead to commands such as rs lint and rs fmt.

    Choose the import style based on the config contents:

    • Prefer simpler static imports when the config is only for an application and its tests, a library and its tests, or a documentation site.
    • If the same config also includes lint, formatting, or staged-file checks, consider dynamically importing dependencies inside the relevant async configuration function. This lets checks skip those dependencies.
    rstack.config.ts
    import { define } from 'rstack';
    
    define.app(async () => {
      const { pluginReact } = await import('@rsbuild/plugin-react');
      return {
        plugins: [pluginReact()],
      };
    });
    
    define.lint(({ js }) => [js.configs.recommended]);
    
    define.fmt({
      singleQuote: true,
    });

    Configuration APIs

    Configuration options follow the formats of the underlying tools. When using APIs and helpers that Rstack CLI re-exports, prefer the rstack/app, rstack/lib, rstack/test, and rstack/lint entry points.

    define.app()

    Defines the Rsbuild configuration for an application. It accepts a configuration object or a configuration function. The function receives the standard Rsbuild configuration parameters.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.app({
      html: {
        title: 'My App',
      },
      output: {
        distPath: {
          root: 'dist',
        },
      },
    });

    define.lib()

    Defines the Rslib configuration for a library. It accepts a configuration object or a configuration function. The function receives the standard Rslib configuration parameters.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.lib({
      dts: true,
      format: 'esm',
    });

    define.doc()

    Defines the Rspress configuration for a documentation site. It accepts a configuration object or an async configuration function.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.doc({
      root: 'docs',
      title: 'My Site',
    });

    @rspress/core is an optional dependency of Rstack CLI. Install it in every project that uses the rs doc command:

    npm
    yarn
    pnpm
    bun
    deno
    npm install -D @rspress/core

    define.test()

    Defines the Rstest configuration. It accepts a configuration object or a configuration function.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.app({
      // Shared application configuration
    });
    
    define.test({
      setupFiles: ['./tests/rstest.setup.ts'],
      testEnvironment: 'happy-dom',
    });

    When extends is omitted, Rstack CLI automatically connects the test configuration to define.app() through the Rsbuild adapter. If no application configuration is defined, it falls back to define.lib() through the Rslib adapter. The application configuration takes precedence when both are defined. Set extends explicitly to opt out of this automatic inheritance.

    If the root test configuration does not define extends and contains projects, Rstack CLI applies automatic inheritance to each inline project that omits its own extends. A function-based application or library configuration is resolved once and shared by those projects. String project entries are passed to Rstest unchanged; they load their external configurations independently and do not inherit the current application or library configuration.

    These rules also apply to shared configurations: Rstack first merges the test settings, then determines whether to inherit the merged App or Lib configuration.

    For more guidance on testing, see Testing.

    define.lint()

    Defines the Rslint configuration. Pass the configuration directly, or use a synchronous or asynchronous function. The function receives all exports from rstack/lint, so presets and plugins do not need to be imported manually.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.lint(({ js, ts }) => [
      js.configs.recommended,
      ts.configs.recommendedTypeChecked,
    ]);

    define.fmt()

    Defines formatting settings for rs fmt. Pass a configuration object directly, or use a synchronous or asynchronous function that returns one.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.fmt({
      printWidth: 100,
      singleQuote: true,
    });

    For detailed usage, see Formatting.

    define.staged()

    Defines the lint-staged configuration used to run tasks on staged Git files. It accepts either an object that maps glob patterns to tasks or a task-generator function. Tasks can be commands, command arrays, or functions supported by lint-staged.

    rstack.config.ts
    import { define } from 'rstack';
    
    define.staged({
      '*.{js,jsx,ts,tsx}': ['rs lint', 'rs fmt'],
      '*.{json,jsonc,md,mdx,css,html,yml,yaml}': 'rs fmt',
    });

    Provide a staged configuration through define.staged() or a shared configuration. rs staged reports an error if neither provides one.

    Shared configurations

    Use define.extends() to share build, test, lint, and formatting settings across projects. A shared configuration is a plain object whose fields accept the same values as the corresponding define.*() APIs. In TypeScript, use RstackConfig to check its types:

    shared.ts
    import type { RstackConfig } from 'rstack';
    
    export const sharedConfig: RstackConfig = {
      test: {
        retry: 2,
      },
      lint: ({ js, ts }) => [js.configs.recommended, ts.configs.recommended],
      fmt: {
        singleQuote: true,
        printWidth: 80,
      },
    };

    Import the shared configuration from a local module or npm package, then add any project-specific settings:

    rstack.config.ts
    import { define } from 'rstack';
    import { sharedConfig } from './shared.ts';
    
    define.extends([sharedConfig]);
    
    define.fmt({
      printWidth: 100,
    });

    The project inherits the shared settings and changes only printWidth to 100.

    Call define.extends() at most once per configuration load, with all shared configurations in a single array.

    Merge order

    Configurations are applied in this order:

    1. Shared configurations in the define.extends() array, from left to right.
    2. The project's own define.*() configurations, even if they appear before define.extends() in the file.

    Each tool's merge rules determine which values are combined and which are replaced.

    Shared configurations can also extend other shared configurations:

    team.ts
    import type { RstackConfig } from 'rstack';
    import { sharedConfig } from './shared.ts';
    
    export const teamConfig: RstackConfig = {
      extends: [sharedConfig],
      test: {
        retry: 3,
      },
    };

    Inherited configurations are applied first. With define.extends([teamConfig]), the order is sharedConfig → teamConfig → project configuration.

    Repeated references are not deduplicated. If both a and b extend base, define.extends([a, b]) applies base → a → base → b → project configuration. Each tool handles duplicate plugins according to its own rules. Circular inheritance throws an error that identifies the circular reference.

    Merge rules

    Rstack merges configurations separately for each tool:

    FieldMerge behavior
    appUses Rsbuild's mergeRsbuildConfig.
    libUses Rslib's mergeRslibConfig.
    testUses Rstest's mergeRstestConfig.
    lintConcatenates configuration arrays in order; Rslint applies their rules.
    fmtShallow merge: later values replace earlier values for the same option. Arrays and objects, including overrides, are replaced as a whole.
    docUses Rspress's mergeDocConfig: recursively merges objects, appends arrays, and replaces ordinary functions with later functions.
    stagedShallow merge by glob pattern: later tasks replace earlier tasks for the same pattern, including command arrays and task functions.

    The doc rules also apply to builderConfig: a later builderConfig.tools.rspack callback replaces an earlier one, while callback arrays are concatenated. When an array is merged with a defined non-array value, Rspress treats that value as a single array item.

    For staged, if either configuration is a top-level task-generator function, the later configuration replaces the earlier one entirely. For example, glob mappings → function → new glob mappings leaves only the new mappings. Task functions run when lint-staged supplies the staged file list.

    Loading dependencies

    Shared configurations can also import dependencies dynamically inside tool configuration functions, so each command loads only what it needs.

    shared.ts
    import type { RstackConfig } from 'rstack';
    
    export const sharedConfig: RstackConfig = {
      app: async () => {
        const { pluginReact } = await import('@rsbuild/plugin-react');
        return {
          plugins: [pluginReact()],
        };
      },
    };

    Resolving paths

    Relative paths follow each tool's existing rules; they are not automatically resolved from the shared module's directory. Use an absolute path for files shipped with the shared configuration:

    shared.ts
    import { fileURLToPath } from 'node:url';
    import type { RstackConfig } from 'rstack';
    
    export const sharedConfig: RstackConfig = {
      test: {
        setupFiles: [fileURLToPath(new URL('./setup.ts', import.meta.url))],
      },
    };

    Make sure the published package includes the referenced file. If you compile it, update the path to match its output location.