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/solution/reactlynx.md.
close
  • English
  • ReactLynx

    In this document, you will learn how to build a ReactLynx component library for Lynx applications with Rslib. You can check out ReactLynx related example projects in Examples.

    Create a ReactLynx project

    You can use @lynx-js/create-lynx to create a ReactLynx component library with Rslib:

    npm
    yarn
    pnpm
    bun
    deno
    npm create @lynx-js/lynx@latest

    Then select Rslib when prompted to "Select build tool", followed by TypeScript or JavaScript. You can also specify the Rslib template directly:

    npm
    yarn
    pnpm
    bun
    deno
    npm create @lynx-js/lynx@latest my-lib -- --template rslib-react-ts

    Use Rslib in an existing project

    To develop a ReactLynx library, you need to set the target to "web" in rslib.config.ts. This is crucial because Rslib sets the target to "node" by default, which differs from the default target of Rsbuild.

    Additionally, ReactLynx component libraries typically need to preserve JSX syntax in the build output so that the application's ReactLynx compiler can process it according to the target environment and build configuration. You can register the Rsbuild React Plugin, set runtime to 'preserve' through swcReactOptions, and set bundle to false to enable bundleless builds. Also, set js to '[name].jsx' in output.filename to emit .jsx files.

    For example, configure rslib.config.ts as follows:

    rslib.config.ts
    import { 
    function defineConfig<const Config extends RslibConfig, const Definition extends Config | ((env: ConfigParams) => Config) | ((env: ConfigParams) => Promise<Config>)>(config: Definition & (Definition extends (...args: never[]) => infer CallbackResult ? [Awaited<CallbackResult>] extends [RslibConfig] ? unknown : never : RslibConfig & Record<Exclude<keyof Definition, keyof RslibConfig>, never>)): Definition extends (...args: never[]) => infer CallbackResult ? [CallbackResult] extends [RslibConfig] ? RslibConfigSyncFn : RslibConfigAsyncFn : RslibConfig (+4 overloads)

    This function helps you to autocomplete configuration types. It accepts a Rslib config object, or a function that returns a config.

    defineConfig
    } from '@rslib/core';
    import {
    const pluginReact: (options?: PluginReactOptions) => RsbuildPlugin
    pluginReact
    } from '@rsbuild/plugin-react';
    export default
    defineConfig<RslibConfig, {
        readonly bundle: false;
        readonly output: {
            readonly target: "web";
            readonly filename: {
                readonly js: "[name].jsx";
            };
        };
        readonly plugins: [RsbuildPlugin];
    }>(config: {
        readonly bundle: false;
        readonly output: {
            readonly target: "web";
            readonly filename: {
                readonly js: "[name].jsx";
            };
        };
        readonly plugins: [RsbuildPlugin];
    } & RslibConfig & Record<never, never>): RslibConfig (+4 overloads)

    This function helps you to autocomplete configuration types. It accepts a Rslib config object, or a function that returns a config.

    defineConfig
    ({
    bundle: false

    Whether to bundle the library.

    @defaultValuetrue@seehttps://rslib.rs/config/lib/bundle
    bundle
    : false,
    output: {
        readonly target: "web";
        readonly filename: {
            readonly js: "[name].jsx";
        };
    } & RslibOutputConfig

    Options for build outputs.

    @inheritdoc
    output
    : {
    target: "web"

    Setting the build target for Rsbuild.

    @override@default'node'
    target
    : 'web',
    filename: {
        readonly js: "[name].jsx";
    } & FilenameConfig

    Sets the filename of output files.

    filename
    : {
    js: "[name].jsx" | ("[name].jsx" & ((pathData: PathData, assetInfo?: AssetInfo) => string))

    The name of the JavaScript files.

    @default

    - dev: '[name].js'

    • prod: '[name].[contenthash:10].js'
    js
    : '[name].jsx',
    }, },
    plugins: [RsbuildPlugin] & RsbuildPlugins

    Configure Rsbuild plugins.

    plugins
    : [
    function pluginReact(options?: PluginReactOptions): RsbuildPlugin
    pluginReact
    ({
    swcReactOptions?: ReactConfig | undefined

    Configure the behavior of SWC to transform React code, the same as SWC's jsc.transform.react.

    swcReactOptions
    : {
    ReactConfig.runtime?: "automatic" | "classic" | "preserve" | undefined

    Decides which runtime to use when transforming JSX.

    • "automatic" - Automatically imports the functions that JSX transpiles to. This is the modern approach introduced in React 17+ that eliminates the need to manually import React in every file that uses JSX.
    • "classic" - Uses the traditional JSX transform that relies on React.createElement calls. Requires React to be in scope, which was the standard behavior before React 17.
    • "preserve" - Leaves JSX syntax unchanged without transforming it.
    @default"classic"
    runtime
    : 'preserve',
    }, }), ], });

    TypeScript

    For ReactLynx projects using TypeScript, set "jsx": "preserve" and "jsxImportSource": "@lynx-js/react" in your tsconfig.json, and add @lynx-js/types to types:

    tsconfig.json
    {
      "compilerOptions": {
        "jsx": "preserve",
        "jsxImportSource": "@lynx-js/react",
        "types": ["@lynx-js/types", "@rslib/core/types"]
      }
    }

    Set dts to true in rslib.config.ts to generate the library's type declarations.

    Output

    Configure the .jsx entry and type declaration entry in package.json, and declare ReactLynx and its type dependencies as peer dependencies:

    package.json
    {
      "name": "reactlynx-scroll-list",
      "type": "module",
      "exports": {
        ".": {
          "types": "./dist/index.d.ts",
          "default": "./dist/index.jsx"
        }
      },
      "types": "./dist/index.d.ts",
      "files": ["dist"],
      "peerDependencies": {
        "@lynx-js/react": ">=0.100.0",
        "@lynx-js/types": ">=4",
        "@types/react": ">=19"
      }
    }

    Testing

    You can use Rstest to test ReactLynx components. First, install the dependencies needed for testing:

    npm
    yarn
    pnpm
    bun
    deno
    npm add @rstest/core @rstest/adapter-rslib @lynx-js/react-rsbuild-plugin @testing-library/dom @testing-library/jest-dom happy-dom -D

    Use the withRslibConfig function from @rstest/adapter-rslib to reuse your Rslib configuration. See Use Rstest for details.

    Also, use the withDefaultConfig function provided by @lynx-js/react to load the ReactLynx test preset, and register the pluginReactLynx plugin from @lynx-js/react-rsbuild-plugin to compile JSX:

    rstest.config.ts
    import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'; 
    import { withDefaultConfig } from '@lynx-js/react/testing-library/rstest-config'; 
    import { withRslibConfig } from '@rstest/adapter-rslib';
    import { defineConfig } from '@rstest/core';
    
    export default defineConfig({
      extends: [withDefaultConfig(), withRslibConfig()],
      plugins: [pluginReactLynx()],
    });

    Once configured, import APIs such as render, screen, and fireEvent from @lynx-js/react/testing-library to test component rendering and interactions.

    See the ReactLynx testing guide for usage details. You can find a complete component testing project in Examples.

    Use the component library

    Use in an application

    In a Lynx application, you can use a ReactLynx component library by importing it as a package or loading it as an External Bundle.

    Import from a package

    The component library built and published with the configuration above preserves JSX. After installing it in a Lynx application, you can import and use its components directly, with their JSX processed by the application's ReactLynx compiler. For example, use the ScrollList exported by the library:

    src/App.tsx
    import { ScrollList } from 'reactlynx-scroll-list';
    
    export function App() {
      return <ScrollList />;
    }

    Load an external bundle

    Lynx applications can also load an External Bundle on demand at runtime. Its JSX has already been compiled during the bundle build. When creating the library, you can select the optional External Bundle tool or enable it with --tools external-bundle in the initialization command:

    npm
    yarn
    pnpm
    bun
    deno
    npm create @lynx-js/lynx@latest my-lib -- --template rslib-react-ts --tools external-bundle

    The generated project includes rslib.external-bundle.config.* and a build:external-bundle script. Run the following command to compile the library into dist-external-bundle/<id>.lynx.bundle:

    npm
    yarn
    pnpm
    bun
    deno
    npm run build:external-bundle

    See the Lynx External Bundle guide for loading and configuration details.

    Use in a component library

    You can use existing shared components in your library and preserve JSX for the application to compile. Distributing the package that provides those components as a dependency is recommended. If you need to publish its code with your library, copy output that is ready to use, or rebuild it when its code, styles, or internal imports need processing.

    Type declarations

    When publishing dependency code with your library by building or copying it, consumers still need to install the dependency if your .d.ts files reference its types. Use dts.bundle.bundledPackages to bundle those type declarations as well, for example by setting it to ['reactlynx-scroll-list'].

    Declare the library that provides the shared components in dependencies or peerDependencies. Rslib marks these dependencies as external by default, preserving their package imports.

    When consumers install your library, their package manager installs or reuses packages according to the declared dependencies. The application build then loads these libraries through their package imports and compiles their JSX together.

    Rebuild dependency output

    If you need to compile the dependency's code or styles, or change its internal imports, use Rslib to build your library and the dependency separately, and use output.externals to rewrite imports.

    Consider a component library that includes a scroll list adapted from reactlynx-scroll-list and other components you write. The configuration has three builds, each identified by an id:

    • components: builds the other components you write in bundleless mode, handling component code that needs to preserve JSX.
    • bundled-components: bundles the scroll list entry and the local TS/JS modules it imports, which can adjust exports or include other TS/JS logic. It uses output.externals to reference the dependency output.
    • vendor: rebuilds the output of reactlynx-scroll-list, preserving JSX and emitting files to dist/vendor/reactlynx-scroll-list.

    In this example, only src/scroll-list/index.ts imports reactlynx-scroll-list. Other components reference this entry through local imports:

    src/scroll-list/index.ts
    export { ScrollList } from 'reactlynx-scroll-list';

    Adjust outBase and the entry to match the dependency's actual output, including the code, styles, and static assets to process:

    rslib.config.ts
    import { dirname } from 'node:path';
    import { fileURLToPath } from 'node:url';
    import { pluginReact } from '@rsbuild/plugin-react';
    import { defineConfig } from '@rslib/core';
    
    const scrollListDir = dirname(
      fileURLToPath(import.meta.resolve('reactlynx-scroll-list')),
    );
    const reactPlugin = pluginReact({
      swcReactOptions: {
        runtime: 'preserve',
      },
    });
    
    export default defineConfig({
      lib: [
        {
          id: 'components',
          bundle: false,
          dts: true,
          source: {
            entry: {
              index: [
                './src/**/*',
                '!./src/scroll-list/**',
              ],
            },
          },
          plugins: [reactPlugin],
        },
        {
          id: 'bundled-components',
          source: {
            entry: {
              'scroll-list/index': './src/scroll-list/index.ts',
            },
          },
          output: {
            externals: {
              'reactlynx-scroll-list': '../vendor/reactlynx-scroll-list/index.jsx',
            },
          },
        },
        {
          id: 'vendor',
          bundle: false,
          outBase: scrollListDir,
          source: {
            entry: {
              index: `${scrollListDir}/**/*.{js,jsx,css,svg}`,
            },
          },
          output: {
            distPath: './dist/vendor/reactlynx-scroll-list',
          },
          plugins: [reactPlugin],
        },
      ],
      output: {
        target: 'web',
        filename: {
          js: '[name].jsx',
        },
      },
    });

    The top-level output.filename setting gives all builds a consistent .jsx extension for their code output. The generated dist/scroll-list/index.jsx imports dist/vendor/reactlynx-scroll-list/index.jsx, so include the entire dist directory when publishing your package.

    If reactlynx-scroll-list imports other component libraries that also need to be distributed with your package, add builds for those packages. In the vendor build, use output.externals to rewrite their package imports to the paths of their output files.

    Copy dependency output directly

    If the dependency's output is ready to use and its internal imports need no changes, copy the complete output with output.copy.

    The following configuration builds the library source in bundleless mode to preserve JSX and copies the dependency's output. As above, only src/scroll-list/index.ts imports the dependency, and the path in output.externals is relative to the generated dist/scroll-list/index.jsx:

    rslib.config.ts
    import { dirname } from 'node:path';
    import { fileURLToPath } from 'node:url';
    import { pluginReact } from '@rsbuild/plugin-react';
    import { defineConfig } from '@rslib/core';
    
    const scrollListDir = dirname(
      fileURLToPath(import.meta.resolve('reactlynx-scroll-list')),
    );
    
    export default defineConfig({
      bundle: false,
      dts: true,
      output: {
        target: 'web',
        filename: {
          js: '[name].jsx',
        },
        externals: {
          'reactlynx-scroll-list': '../vendor/reactlynx-scroll-list/index.jsx',
        },
        copy: [
          {
            from: scrollListDir,
            to: 'vendor/reactlynx-scroll-list',
          },
        ],
      },
      plugins: [
        pluginReact({
          swcReactOptions: {
            runtime: 'preserve',
          },
        }),
      ],
    });