Building a React Native Component Library with Lerna, Yarn Workspaces, and Automated Tooling
This comprehensive tutorial walks through creating a React Native component library using Lerna monorepo, Yarn workspaces, code standards, automated builds, unit testing, documentation with Dumi, and on-demand loading techniques, providing practical guidance for developers to manage, test, and publish reusable UI components.
Introduction
The article presents a step‑by‑step guide for building a production‑ready React Native component library, covering repository management, component design, unit testing, CI/CD, and documentation.
Choosing the Base UI Library
After evaluating vant , antd-mobile and fishd-mobile , the author selects vant as the design reference and creates a new package named vant-react-native with appropriate name and description fields in package.json :
{
"name": "vant-react-native",
"description": "Lightweight React Native UI Components inspired on Vant"
}Monorepo Architecture with Lerna
Lerna is used to manage a monorepo (single repository) that contains multiple packages. The advantages include component‑level decoupling, independent versioning, on‑demand imports, clear dependency control, and easier contribution.
.
└── packages
├── button # @vant-react-native/button
└── icons # @vant-react-native/iconInitializing the Lerna Project
$ mkdir vant-react-native && lerna init --independentYarn Workspaces Integration
Enable useWorkspaces in lerna.json and configure package.json workspaces:
{
"npmClient": "yarn",
"useWorkspaces": true
}
{
"private": true,
"workspaces": ["packages/*"]
}Lerna Publish Configuration
Set up ignoreChanges and command.publish.registry to control version bumps and publishing:
{
"ignoreChanges": ["ignored-file", "**/__tests__/**", "**/*.md"],
"command": {
"publish": {
"registry": "https://registry.npmjs.org"
}
}
}Standardized Git Commit Messages
The author uses Conventional Commits via the @youngjuning/cli tool, providing an init-commit command that configures commitizen , cz-customizable , and commitlint . Husky is replaced by yorkie for Git hooks.
Code Style Enforcement
ESLint, Prettier, and EditorConfig are set up using the author's own configs ( @youngjuning/eslint-config , @youngjuning/prettier-config ). Example ESLint installation and configuration:
yarn add -D eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y \
eslint-plugin-import \
eslint-plugin-react-native
// .eslintrc.js
module.exports = {
extends: ['@youngjuning/eslint-config/react-native']
};Creating the First Component – Icons
Icons are generated from an Iconfont service using react-native-iconfont-cli . The workflow includes creating the icons package, generating iconfont.json , and building the React Native components:
# Create packages
$ lerna create vant-react-native -y
$ lerna create @vant-react-native/icons -y
# Generate iconfont config
$ npx iconfont-init # creates iconfont.json
# Build icons
$ npx iconfont-rn
# package.json scripts for icons
{
"build": "npx iconfont-rn",
"prepublishOnly": "yarn build"
}Exporting Packages and TypeScript Configuration
The main package re‑exports the icon and button modules, and a shared tsconfig.base.json is used for all sub‑packages:
// packages/vant-react-native/src/index.ts
export * from './icon';
export * from './button';Publishing the Library
First release uses lerna publish 0.0.1 (or sets the initial version to 0.0.0 and runs lerna publish ). The author also mentions checking the published package via jsDelivr.
Development & Debugging Workflow
Because Metro does not support symlinks outside the project root, the author ensures that Lerna’s hoisted packages remain within the root. The typical development loop is:
Modify code.
Run lerna run build to compile each package.
Run yarn ios (or yarn start --reset-cache ) to launch the app.
Repeat.
Real‑time compilation scripts are added (e.g., tsc -w for dev, onchange -i 'iconfont.json' -- yarn build for icons) and orchestrated with npm-run-all :
{
"predev": "lerna run build --scope @vant-react-native/icons",
"dev": "lerna run dev --parallel",
"start": "react-native start",
"debug": "run-p dev start"
}On‑Demand Loading
Since Metro cannot tree‑shake, the author uses babel-plugin-import to rewrite imports to specific sub‑folders, enabling on‑demand loading of components and icons. Example Babel config:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
["import", { libraryName: 'vant-react-native' }]
]
};A custom plugin ( babel-plugin-import-vant ) is also provided to map named imports like import { Button } from 'vant-react-native' to import Button from '@vant-react-native/button' .
Documentation with Dumi
The library uses Dumi (with dumi-theme-mobile and umi-plugin-react-native ) to generate an interactive documentation site. Key steps include installing Dumi, creating .umirc.ts , adding build scripts, and configuring CI for GitHub Pages and Surge preview deployments.
Unit Testing
Jest and Enzyme are configured for React Native unit tests. The jest.config.js includes preset react-native , coverage collection, module name mapping for assets, and snapshot serializers. A sample test for the Button component demonstrates shallow rendering and snapshot verification.
Conclusion
The tutorial equips developers with a full pipeline—from monorepo setup, code quality enforcement, component generation, on‑demand loading, documentation, to CI/CD—enabling the creation and maintenance of a robust React Native UI component library.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.