react-native-builder-bob

repository·main·Indexed 25 days ago

https://github.com/callstack/react-native-builder-bob

A suite of CLI tools to scaffold and build React Native libraries for various targets. It includes create-react-native-library for project scaffolding and the bob CLI for compiling JavaScript files. Supported build targets include ES modules, CommonJS, TypeScript, Flow, and React Native Codegen for the New Architecture.

Tokens
16K
Snippets
47
Records
83
Agent score
85%

What's inside react-native-builder-bob

  1. Configure package.json for ESM and TypeScript compatibility

    main

    To ensure your library works across different environments (Node.js, modern bundlers, and legacy tools), your package.json should include main, types, and exports fields.

    • main: Used by legacy tools like Metro < 0.82.0.
    • types: Used by legacy TypeScript setups (e.g., moduleResolution: "node10").
    • exports: The modern standard for Node.js 12+ and modern bundlers. It uses conditional exports to resolve the correct entry point.

    Note: If you provide an Expo Config plugin (e.g., ./app.plugin.js), you must explicitly expose it in the exports field so other tools can access it.

    "main": "./lib/module/index.js",
    "types": "./lib/typescript/src/index.d.ts",
    "exports": {
      ".": {
        "my-library-source": "./src/index.tsx",
        "types": "./lib/typescript/src/index.d.ts",
        "default": "./lib/module/index.js"
      },
      "./package.json": "./package.json",
      "./app.plugin.js": "./app.plugin.js"
    }
  2. Scaffold a React Native library

    main

    Use create-react-native-library to scaffold a new React Native library project with pre-configured tools including TypeScript, Turbo Modules, Fabric, Kotlin (Android), Swift (iOS), and react-native-builder-bob. The CLI will prompt you with configuration questions to generate the project folder.

    npx create-react-native-library@latest awesome-library
  3. Configure the `codegen` build target for React Native New Architecture

    main

    The codegen target generates React Native Codegen scaffold code. To use this, follow these integration steps:

    1. Add target: Add "codegen" to your targets in package.json or bob.config.js.
    2. Install CLI: Add @react-native-community/cli as a devDependency.
    3. Configure Codegen: Add codegenConfig to package.json:
      "codegenConfig": {
        "outputDir": {
          "ios": "ios/generated",
          "android": "android/generated"
        },
        "includesGeneratedCode": true
      }
    4. Update iOS Imports: Update Turbo Module or Fabric View imports to use the new paths (e.g., #import <YourProjectName/YourProjectNameSpec.h>).
    5. Configure Android: Add a react-native.config.js at the root with the cmakeListsPath:
      module.exports = {
        dependency: {
          platforms: {
            android: {
              cmakeListsPath: 'generated/jni/CMakeLists.txt',
            },
          },
        },
      };
    6. Automate Build:
      • Android: Add a invokeLibraryCodegen task to example/android/app/build.gradle that runs npx bob build --target codegen.
      • iOS: Add a pre_install hook to example/ios/Podfile that runs npx bob build --target codegen.
  4. Configure tool-specific conditions to avoid dual package hazard

    main

    Instead of providing both import and require conditions, you can use tool-specific conditions in the exports field. This allows each tool to load the appropriate build without creating separate instances in the same runtime.

    Example setup for Webpack, Vite, Rollup, Metro (React Native), and Node.js 22.10.0+:

    • react-native: For Metro in React Native.
    • module: For bundlers like Webpack, Vite, or Rollup.
    • module-sync: For Node.js 22.10.0+ (handles both import and require).
    • default: Fallback for other environments.

    If using TypeScript, you may need to configure customConditions in your tsconfig.json to resolve these correctly.

    {
      "main": "./lib/commonjs/index.js",
      "module": "./lib/module/index.js",
      "types": "./lib/typescript/commonjs/src/index.d.ts",
      "exports": {
        ".": {
          "react-native": {
            "types": "./lib/typescript/module/src/index.d.ts",
            "default": "./lib/module/index.js"
          },
          "module": {
            "types": "./lib/typescript/module/src/index.d.ts",
            "default": "./lib/module/index.js"
          },
          "module-sync": {
            "types": "./lib/typescript/module/src/index.d.ts",
            "default": "./lib/module/index.js"
          },
          "default": {
            "types": "./lib/typescript/commonjs/src/index.d.ts",
            "default": "./lib/commonjs/index.js"
          }
        },
        "./package.json": "./package.json"
      }
    }
  5. Upgrade the `react-native` version in a generated project

    main

    To upgrade react-native for development and testing purposes in a project generated by react-native-builder-bob, perform the following:

    1. Update Root Dependencies: Bump the versions of react-native, react, @types/react, and @types/react-native (and related packages like react-test-renderer) under devDependencies in your root package.json.
    2. Update Example App: Upgrade react-native in the example app following standard React Native upgrade procedures (Expo or CLI).

    Note: Ensure that the versions of react and react-native match exactly between example/package.json and the root package.json to avoid issues.

  6. Manually configure react-native-builder-bob

    main

    To manually integrate react-native-builder-bob into an existing project, follow these steps:

    1. Install the package as a development dependency:

      yarn add --dev react-native-builder-bob
    2. Configure build targets in your package.json under the react-native-builder-bob key (see Configuration Options for details).

    3. Add a build script to your package.json. It is recommended to use the prepare script to ensure compatibility across different package managers and Git installations:

      "scripts": {
        "prepare": "bob build"
      }
    4. Set up entry points in package.json (e.g., main, types, exports) to point to the generated files in your output directory.

    5. Ignore generated files by adding your output directory (e.g., lib/) to .gitignore and .eslintignore.

    6. Configure Jest to ignore the output directory by adding it to modulePathIgnorePatterns in your Jest configuration.

    "scripts": {
      "prepare": "bob build"
    }
  7. Configure a dual package setup for ESM and CommonJS

    main

    If you need to support both ES modules (ESM) and CommonJS (CJS) environments, you can configure a dual package setup. This involves creating two builds of your library and mapping them via the exports field in package.json.

    1. Update Bob configuration

    Add the commonjs target to your react-native-builder-bob configuration in package.json or bob.config.js:

    "react-native-builder-bob": {
      "source": "src",
      "output": "lib",
      "targets": [
        ["module", { "esm": true }],
        ["commonjs", { "esm": true }],
        "typescript"
      ]
    }

    2. Configure package.json exports

    Update the exports field to include import (for ESM) and require (for CJS) conditions. Each condition should include a types field for TypeScript and a default field for the JS code.

    3. Optional compatibility fields

    • main: Point to the CommonJS build to support tools that do not recognize the exports field.
    • module: Point to the ESM build for tools that use this non-standard field.
    • types: Point to the CommonJS type definitions to support legacy TypeScript configurations (e.g., moduleResolution: "node10").
    {
      "main": "./lib/commonjs/index.js",
      "module": "./lib/module/index.js",
      "types": "./lib/typescript/commonjs/src/index.d.ts",
      "exports": {
        ".": {
          "import": {
            "types": "./lib/typescript/module/src/index.d.ts",
            "default": "./lib/module/index.js"
          },
          "require": {
            "types": "./lib/typescript/commonjs/src/index.d.ts",
            "default": "./lib/commonjs/index.js"
          }
        },
        "./package.json": "./package.json"
      }
    }
  8. Configure tooling to resolve library source conditions

    main

    If your library uses a custom condition (like my-library-source) to resolve source files during development, you must configure your tools to recognize this condition:

    Jest

    Add the condition to testEnvironmentOptions.customExportConditions. When using the React Native Jest preset, ensure you include the default React Native conditions as well.

    Vite

    Add the condition to resolve.conditions in your Vite configuration.

    react-native-monorepo-config

    Pass the condition to the conditions array in the Metro configuration.

    // Jest
    {
      "jest": {
        "preset": "@react-native/jest-preset",
        "testEnvironmentOptions": {
          "customExportConditions": ["require", "react-native", "my-library-source"]
        }
      }
    }
    // Vite
    import { defineConfig } from 'vite';
    export default defineConfig({
      resolve: {
        conditions: ['my-library-source'],
      },
    });
    // react-native-monorepo-config
    const config = withMetroConfig(getDefaultConfig(__dirname), {
      root,
      dirname: __dirname,
      conditions: ['my-library-source'],
    });