⚠️ this article for nuggets community first contract article, not authorized to forbid reprint

Hello everyone, my name is Luozhu 🎋, a wooden front end in Hangzhou, 🧚🏻♀️. If you like my article 📚, please click “like” to help me gather “jinli”.

Luo zhu has a friend, Xiao Hei, who was recently asked in an interview how to design a front-end component library. The inexperienced little black answered business extraction encapsulation into libraries and antD based combined business secondary encapsulation. Finally, Little black was not enough to die by HR. This question is not about empty concepts, but about developer repository management, component design, unit testing, continuous integration, collaboration management, and so on. In order to enable Hei to answer this question perfectly, I decided to lead Hei in building a React Native component library step by step.

This is a practical tutorial on building a component library. It covers general code specifications, submission specifications, document maintenance, unit testing, GitHub Action configuration, and so on. It also involves the multi-package management architecture based on LERNA, the construction of React Native icon library, the development and debugging of React Native component library, and the principle and implementation of on-demand loading. The idea of engineering is universal, so no matter what framework you use, this article is worth reading.

If the computer before digging friends are also interested in component library development, might as well first give a thumbs-up, and then continue to pay attention to luo Zhu and small black component library development journey. PS: With the warehouse and component library documentation to read this article better yo!

Stand on the shoulders of Vant Design

Maintaining and developing a component library takes a lot of time and effort, and flags go up and down again. The first step is to be self-aware. In the absence of designers and amateur developers, I chose to start the component library development journey by implementing React Native versions of existing UI Design. After researching Vant, FishD-Mobile, and ANTD-Mobile, I chose Vant. This is a comparison of the current situation of several warehouses:

Component library team Github Star Npm weekly downloads Maintenance degrees
vant Have a great 17.7 K. 27789 Dimensional height is high, popularity is also high
antd-mobile Ant Design Team 8.9 K. 31470 There is little maintenance and the ants are said to have no use for their interiors
fishd-mobile Netease Cloud business front end 29 22 It certainly looks like a KPI project

The direction of the journey is defined by giving our component library a proper name and number, which in front end engineer terms is the name and description fields of package.json:

// package.json
{
    "name": "vant-react-native"."description": "Lightweight React Native UI Components inspired on Vant"
}
Copy the code

As our component library was positioned as the RN version of Vant, we named the component library vant-react-native in reference to lottie-React-native, Styled react-native and Jpush-react-native naming methods. It is also expected that the component library will be officially supported by Vant when it is completed.

Multi-package management architecture based on Lerna

Lerna is a management tool for managing JavaScript projects that contain multiple packages. The warehouses managed by Lerna are generally called monorepo. The advantages of lerNa-based multi-package management architecture are:

  • Component level decoupling, independent version control, each component has a version record to track
  • Components are released separately, supporting grayscale, version rollback, and smooth promotion and degradation
  • On-demand reference: Users can install a specific component package without configuration.
  • Separation of concerns to reduce large complexity and clear and manageable dependencies between components
  • Single responsibility principle, reduce the participation and contribution of open source gay friends difficulty
.├ ─ exercises ├─ button# @vant-react-native/button└ ─ ─ the ICONS# @vant-react-native/icon
Copy the code

Initialize the LERNA project

$ mkdir vant-react-native && lerna init --independent
Copy the code

yarn workspaces

Using YARN workspaces combined with Lerna useWorkspaces can achieve Lerna reactive power. This isn’t unnecessary; it allows you to manage dependencies in one place (the root directory), which saves both time and space.

Configuration lerna. Json:

{..."npmClient": "yarn"."useWorkspaces": true
}
Copy the code

After hosting yarn Wrokspace, lerna packages will be overwritten by workspaces of top-level package.json:

{
  "private": true."workspaces": [
    "packages/*"],}Copy the code

lerna publish config

If you don’t want to explicitly set your registry configuration separately in all package.json files, such as when using a private registry, setting command. Publish. registry is useful. IgnoreChanges is configured to avoid unnecessary version upgrades.

"ignoreChanges": [
  "ignored-file"."**/__tests__/**"."**/*.md"]."command": {
  "publish": {
    "registry": "https://registry.npmjs.org"}}Copy the code

In addition, if your package name has scope, you need to set publishconfig. access to “public” in the package.json of that package.

lerna version config

When conventionalCommits are set to true, LERNA uses Conventional Commits to determine the version upgrade and generate changelog. md.

"command": {
  "version": {
    "conventionalCommits": true."message": "chore(release): publish"}}Copy the code

Normalized submission

Normalized Git Commit plays an important role in improving git log readability, controllable version control and Changelog generation. In his previous article, Normalize Git Commit, Luo explained in detail the concepts of Conventional Commits, as well as commitizen, CZ-Customizable, @commitLint /cli, Yorkie, and Other aspects of Conventional Commits Settings of tools such as commitlint-config-cz.

Due to the complicated configuration, I added the init-commit command in @youngjuning/ CLI to configure Conventional COMMIT with one click. You can open the COMMIT to see the configuration information.

Note: The higher version of Husky is not backward compatible, so I used a larger yorkie instead of husky in this commit.

Code normalization

The importance of code normalization is self-evident, code normalization involves tools such as EditorConfig, ESLint, prettier, etc. In its | don’t have to worry about the context of ESLint configuration step by step I showed you how to build their own ESLint config plug-in and output @ youngjuning/ESLint – config and @ youngjuning/prettier – config.

Vant-react-native temporarily uses @Youngjuning /eslint-config, @Youngjuning /prettier-config to constrain the project code specification. The following describes related configurations.

eslint

First install the required plug-ins for React-Native.

yarn add -D eslint-plugin-react \
  eslint-plugin-react-hooks \
  eslint-plugin-jsx-a11y \
  eslint-plugin-import \
  eslint-plugin-react-native
Copy the code

Then configure.eslintrc.js

// .eslintrc.js
module.exports = {
  extends: ['@youngjuning/eslint-config/react-native']}Copy the code

prettier

// .prettierrc.js
module.exports = require('@youngjuning/prettier-config');
Copy the code

@youngjuning/eslint-config plans are also managed using LERna, Output @youngjuning/eslint-config-react, @youngjuning/eslint-config-react-native, @Youngjuning /eslint-config-vue Let developers do not need to configure too much out of the box.

editorconfig

# .editorconfig
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.gradle]
indent_size = 4

[BUCK]
indent_size = 4
Copy the code

yorkie & lint-staged

$ yarn add -D yorkie lint-staged
Copy the code
{
  "gitHooks": {
    "commit-msg": "commitlint -e -V"."pre-commit": "lint-staged"
  },
  "lint-staged": {
    "**/*.{js,jsx,ts,tsx}": [
      "eslint --fix"."git add ."]}}Copy the code

The first component starts with Icon

A mature component library will have its own set of ICONS, which are usually designed by the designer using Sketch and exported as AN SVG file.

The SVG files of ant-design-Icons are saved locally, and then react components, VUE components and icons-React-native components are generated through scripts. We don’t need to implement them ourselves because of the complete framework supported. RN we go straight to icons-react-native.

Vant and Fishd-Mobile maintain SVG files with Iconfont, and then implement Icon components by setting @font-face, as shown in the figure:

With TTF files, we can use scripts to generate Icon components based on TTF files like @ant-design/ icons-React-native. However, the disadvantage of using TTF fonts is that every time the Icon is updated, the TTF file is updated accordingly. And then it’s packaged and released again. Also, TTF does not support multi-color ICONS, resulting in all ICONS being monochrome. If you are using React-native Vector-Icons, the library contains more than 10 sets of TTF files, which together amount to about 2M. You probably won’t need them, but they’ll still be packaged into your APP, which is one of the reasons I think the React-Native Elements library is so weak.

So how do we implement the React Native version of Vant-icons with only the Iconfont link? Instead of writing scripts, luo used a tool called React-native Iconfont CLI. Fwh1990 used pure Javascript to convert iconfont to react component for the above pain points. No need to rely on TTF font files, no need to manually download the icon to the local.

Create the LERNA subpackage

# Create the main package, which is used to export all components uniformly
$ lerna create vant-react-native -y
Create the ICONS package, our first component!
$ lerna create @vant-react-native/icons -y
Copy the code

Our directory structure looks like this:

│ ├── ICONS │ ├── README. Md │ ├── react-native │ ├─ README. Md │ ├── package.jsonCopy the code

Generate the ICONS

Installing a plug-in

yarn workspace @vant-react-native/icons add -D react-native-svg react-native-iconfont-cli
Copy the code

Generating a configuration file

The NPX iconfont init command in the packages/ ICONS directory will generate an iconfont. Json file:

{
  "symbol_url": "https://at.alicdn.com/t/font_2553510_7cds497uxwn.js"."use_typescript": false."save_dir": "./lib"."trim_icon_prefix": "van-icon"."default_icon_size": 18
}
Copy the code

Generate React Native standard components

Execute the NPX iconfont-rn command to generate the React Native component. Due to the large number of icon files, we do not add icon products to Git management. So we need to execute the build command before NPM is released:

{
  "build": "npx iconfont-rn"."prepublishOnly": "yarn build"
}
Copy the code

Configure the react – native – vant

We mentioned earlier that packages/vant-react-native is the directory of the main package and we need to add the @vant-react-native/ ICONS package to the dependencies of the main package and export it.

Add the dependent

$ lerna add @vant-react-native/icons --scope vant-react-native
Copy the code

Export Icon component

// packages/vant-react-native/src/index.ts
export { default as Icon } from '@vant-react-native/icons';
export * from '@vant-react-native/icons';
Copy the code

Tsconfig configuration

We expect to use the same configuration for each subpackage, so we will first create tsconfig.base. json in the root directory of the entire project and inherit from the subpackage.

{
  "extends": ".. /.. /tsconfig.base.json"."compilerOptions": {
    "outDir": "lib",},"include": ["src/**/*"]}Copy the code

Configuring the Release Script

As with the @vant-React-native/ICONS subpackage, we need to add the build and prepublishOnly scripts:

{
  "build": "tsc"."prepublishOnly": "yarn build"
}
Copy the code

Publish a package

For the first publish, note that lerna publish 0.0.1 is used, because lerna’s publish command does not have the first publish parameter, so you need to specify the initial version. Or you can set the initial version to 0.0.0 and publish it.

Tip: If you want to view the package after publishing, you can do so through jsdelivr. For example, vant-react-native and @vant-react-native/ ICONS have just been released

Development and debugging

A complete and well-experienced debugging process not only allows you to verify that a component meets expectations during the development phase, but also makes it easier for gay friends in the open source community to participate. The React Native component library debugging process is basically the same as other technology stack processes, but because Metro does not support soft connectivity and Vant-React-Native is a single warehouse project based on LERna, our configuration will be different.

Initialize the React Native App

Since this is a React Native project, we need to initialize a React Native project. First, find a place to create a new React Native App using React-native init Vantapp –template React-native -template-typescript. We then merge the generated App with our main project. The combined project structure is as follows:

. ├ ─ ─ App. The TSX ├ ─ ─ __tests__ │ └ ─ ─ App - test. The TSX ├ ─ ─ android │ ├ ─ ─ App │ ├ ─ ─ build. Gradle │ ├ ─ ─ gradle │ ├ ─ ─ │ ├── gradlew │ ├── gradlew. Bat │ ├─ Settings.gradle ├── app.json ├─ babel.config.js ├─ Commitlint.config.js │ ├── ios │ ├─ Podfile │ ├─ Podfile. Lock │ ├── Podfile. Lock │ ├── Pods │ ├─ vantapp │ ├─ Vantapp. Xcodeproj │ ├ ─ ─ vantapp. Xcworkspace │ └ ─ ─ vantappTests ├ ─ ─ lerna. Json ├ ─ ─ metro. Config. Js ├ ─ ─ package. The json ├ ─ ─ │ ├── vant-React-native ├── tsconfig.base. Json ├── tsconfig.json ├─ yarn.lockCopy the code

The main conflict is the configuration of tools such as Prettier and ESLint, which is easy to merge. Before we can run a project, we usually need to compile it. We can batch run build NPM script in subpackages with lerna run build command.

Note 📢 : Because of dependencies between subpackages, do not use the — PARALLEL parameter to execute the package script in parallel.

Now let’s write a nine-grid Demo to verify:

// App.tsx
import React, { Component } from 'react';
import { View, Text, SafeAreaView, ScrollView } from 'react-native';
import { Icon } from 'vant-react-native';
// We can also just install the @vant-React-native/ICONS package
// import { VanIconAdd } from '@vant-react-native/icons'

type IconNameType = React.ComponentProps<typeof Icon>['name'];

export default class App extends Component {
  render() {
    return (
      <SafeAreaView>
        <ScrollView>
          <Text
            style={{ textAlign: 'center', paddingVertical: 20.fontSize: 25.color: '#007fff'}} >
            vant-react-native
          </Text>
          <View style={{ flexWrap: 'wrap', flexDirection: 'row' }}>
            {data.map((item, index) => {
              const lastLineLength = data.length % 4 || 4;
              return (
                <View
                  key={item}
                  style={{
                    width: '25% ',marginBottom: index < data.length - lastLineLength ? 40 : 0.alignItems: 'center'}} >
                  <Icon name={item} size={40} />
                  <Text style={{ color: '#646566', marginTop: 10}} >{item}</Text>
                </View>
              );
            })}
          </View>
        </ScrollView>
      </SafeAreaView>); }}const data: IconNameType[] = ['location-o'.'like-o'.'star-o'.'phone-o'.'setting-o'.'fire-o'.'coupon-o'.'cart-o'.'shopping-cart-o'.'cart-circle-o'.'friends-o'.'comment-o'.'gem-o'.'gift-o'.'point-gift-o'.'send-gift-o'.'service-o'.'bag-o'.'todo-list-o'.'balance-list-o'.'close'.'clock-o'.'question-o'.'passed'];
Copy the code

Then run yarn ios to see the actual effect (then we can run yarn start –reset-cache to quickly start debugging) :

In the example code above we can see that we directly use import {Icon} from ‘vant-react-native’; Instead of relative paths referencing modules under Packages. However, our project did not install this dependency. How did the compiler find it? There’s no silver bullet here either, because lerna will soft link subpackages to node_modules, and we can use ls-al to see where the packages actually point:

We can also see in the type prompt that we are actually pointing to files under Packages:

Note 📢 : Metro does not support symbolic links where the soft link is not under the project root. In this case, the soft link is still under the root, so it works correctly ✅. This feature ensures consistency and convenience between debugging and production development.

Real-time compilation

Now our debugging process is:

  1. Modify the code
  2. performlerna run buildCompile each subpackage
  3. performyarn iosDebugging project
  4. Modify the code
  5. performlerna run buildrecompile
  6. performyarn start --reset-cacheRun the project
  7. Loop 4, 5, 6.

Although React Native has Fast Refresh, since our code needs to be compiled, we need to repeat the compile run action.

Any repetitive work can be replaced with scripts. First we need to add scripts to each subpackage to compile in real time. Scripts like Rollup, Babel, Webpack, typescript have parameters to compile in real time:

{
  "scripts": {
    "dev": "tsc -w"."build": "tsc"."prepublishOnly": "yarn build"}},Copy the code

However, the NPX iconfont used in @vant-React-native/ICONS package has no real-time compilation option. After investigation, I introduced the onChange library, which can listen to the changes of files and execute a command based on glob mode:

{
  "scripts": {
    "dev": "onchange -i 'iconfont.json' -- yarn build",}}Copy the code

Then we need to batch execute the real-time compiled script using lerna run dev — PARALLEL. Parallel is added here because the process will get stuck if the subpackage is compiled in real-time. To remedy this, we had to pre-compile the @vant-react-native/ ICONS package. Then for the same reason I introduced npm-run-all to execute lerna run dev and React-native start in parallel. The complete script is as follows:

{
  "predev": "lerna run build --scope @vant-react-native/icons"."dev": "lerna run dev --parallel"."start": "react-native start"."debug": "run-p dev start",}Copy the code

According to the need to load

Hei: “Luo Zhu, I introduced the entire component library in order to use a few of the react-Native Elements components. Because the component library relies on React-native vector-Icons, the bundle becomes larger. If I just want to use the whole Vant-React-Native, how do I solve this problem?”

It is well known that Metro, the React Native packaging tool, does not support tree-shaking. The solution to this problem is actually quite simple, and you might be savvy enough to know that with babel-plugin-import it is possible to load on demand. But since we are a multi-package management architecture, we need to design a solution for a multi-package architecture.

The react – naitve bundle package

To compare the package size before and after optimization, we need to use the React-native bundle command to look at the size of the pure JS package. Let’s take a quick look at this command:

react-native bundle --platform ios --entry-file index.js --bundle-output ./bundle/ios/index.ios.jsbundle --assets-dest ./bundle/ios --dev false --reset-cache
Copy the code
  • --entry: Entry JS file
  • --bundle-output: Indicates the path of the generated bundle file
  • --platformPlatform:
  • --assets-dest: The output directory of image resources
  • --dev: if it is a development version, we will set it to false when typing the official installation package
  • --reset-cache: resets the cache to avoid packaging with old caches

Load on demand principle

We mentioned that packages/vant-react-native have only one file SRC /index.ts to export all subpackages. Now we add a new package, Button, that looks like this:

export { default as Icon } from '@vant-react-native/icons';
export * from '@vant-react-native/icons';
export { default as Button } from '@vant-react-native/icons';
Copy the code

In this way, users can only import Button from ‘@vant-react-native/ Button ‘; Or import Button from ‘vant-react-native/lib/ Button ‘; This is not only inconvenient for developers to use, but also adds a lot of bytes in terms of packaging products. So the question is, what kind of organization would satisfy on-demand loading? The answer is in the documentation for the babel-plugin-import plug-in:

As you can see from the figure, the babel-plugin-import plug-in refers to the module folder during compilation. The user installs the plug-in and does the following configuration to complete the on-demand loading.

"plugins": [["import", { libraryName: "antd", style: true}]]Copy the code

Still no silver bullet, the plugin just takes the place of your right hand. With this in mind, we can reorganize our Vant-React-Native package in the format required by the documentation:

.├ ── Changelo.md ├─ lib# Compile product uploaded to NPM│ ├ ─ ─ the buttonThe default configuration of babel-plugin-import is met│ │ ├ ─ ─ the index, which s │ │ └ ─ ─ index. The js │ ├ ─ ─ icon │ │ ├ ─ ─ the index, which s │ │ └ ─ ─ index. The js │ ├ ─ ─ the index, which s │ └ ─ ─ index, js# export * from './button';├ ─ ─ package. Json ├ ─ ─ the SRC# source directory│ ├ ─ ─ button │ │ └ ─ ─ but ts │ ├ ─ ─ icon │ │ └ ─ ─ but ts │ └ ─ ─ but ts └ ─ ─ tsconfig. JsonTo compile the configuration, compile the ts file into the lib folder
Copy the code

Vant – react – native/SRC/button/index. The ts:

import Button from '@vant-react-native/button';
export default Button;
export { Button };
Copy the code

Vant – react – native/SRC/icon/index. The ts:

import Icon from '@vant-react-native/icons';

export default Icon;
export { Icon };
export * from '@vant-react-native/icons';
Copy the code

Vant – react – native/SRC/index. The ts:

export * from './icon';
export * from './button';
Copy the code

Then modify babel.config.js in the project:

module.exports = {
  presets: ['module:metro-react-native-babel-preset'].plugins: [["import", {libraryName: 'vant-react-native'}]],};Copy the code

Write a Babel plug-in?

This can be accomplished by modifying the way the main package is exported, but it greatly increases the complexity of the project itself. We already know that babel-plugin-import works by transforming the reference path. Import {Button} from ‘vant-react-native’ to import Button from ‘@vant-react-native/ Button ‘ The answer is yes. Here is a set of configurations I wrote based on the customName configuration of Babel-plugin-import and encapsulated in the babel-plugin-import-vant package:

import camelCase from 'camelcase';

export default() :any[] => [
  [
    'import',
    {
      libraryName: 'vant-react-native'.customName: (name: string) = > {
        if (name === 'icon') {
          return '@vant-react-native/icons';
        }
        if (name.match(/^van-icon-/)) {
          return `@vant-react-native/icons/lib/${camelCase(name, { pascalCase: true })}`;
        }
        return `@vant-react-native/${name}`; }},'vant-react-native',], ['import',
    {
      libraryName: '@vant-react-native/icons'.customName: (name: string) = > {
        return `@vant-react-native/icons/lib/${camelCase(name, { pascalCase: true })}`; }},'@vant-react-native/icons',]];Copy the code

Add plugins to your project’s babel.config.js configuration: […require(‘babel-plugin-import-vant’).default()] to enable on-demand loading.

Is there anything else that can be optimized? If you are smart, you may find that I only exported a configuration through a function, not a real plug-in. So in the future, I will customize vant-React-Native’s own on-demand Babel plug-in.

Name.match (/^van-icon-/) this is because the @vant-react-native/ ICONS package contains not only the default exported icon component, but also many individual icon components. To further reduce the size of the package, This subpackage was also loaded on demand.

We already know that the principle of on-demand loading is that there is no middleman to make a difference and directly talk to the seller, so later when we meet similar demands, we can return to the seller’s address through conversion. There is no need to destructively change the project structure.

Results show

Initial package size Load on demand not configured (import Button) Load on Demand (introduce Button) Load on Demand (import Icon) Load on Demand (introduce VanIconAdd)
723KB 1.8 M 725KB 1.8 M 1.22 M

The reason why the Icon package is large is because the React-Native SVG library is large. Therefore, it is not recommended to use the Icon component directly. Instead, use separate Icon components such as VanIconAdd and VanIconEye.

Component library document

Interactive Demo is more important for component library documents. I am an experienced user of Dumi. With the help of Dumi-theme-mobile and UMi-plugin-React-native, we can well meet the requirements of building react Native component library documents.

Integrate Dumi into the project

Install dependencies:

$ yarn add dumi dumi-theme-mobile umi-plugin-react-native -D
Copy the code

Configuration file:

Add.umirc.ts to the project root directory

import { defineConfig, IConfig } from 'dumi';

export default defineConfig({
  title: 'vant-react-native'.mode: 'site'.logo: 'https://img01.yzcdn.cn/vant/logo.png'.favicon: 'https://img01.yzcdn.cn/vant/logo.png'.resolve: {
    includes: ['docs'.'packages/button'.'packages/icons'],},// more config: https://d.umijs.org/config
} as IConfig);
Copy the code

It is worth mentioning that Dumi supports Lerna repository, which by default searches for Markdown documents for all subpackages and generates routes based on packages/[package name]/ SRC. The resolve.includes directory is used to configure the document directory that Dumi sniffs, and Dumi tries to recursively search for markdown files in the configured directory.

Add the NPM script:

Note 📢 : due to the actual relies on the packages of the package, we need to compile all the packages, or deployment time will quote This dependency was not found: the error.

{
  "scripts": {
    "start:dumi": "dumi dev"."build:dumi": "lerna run build && dumi build"}}Copy the code

Ignore the file (.gitignore) :

# umi
.umi
.umi-production
.env.local
dist/
Copy the code

Deploy to GitHub Pages

Create.github/workflows/gh-pages in the root directory:

name: github pages
on:
  push:
    branches:
      - main # default branch
jobs:
  deploy:
    runs-on: Ubuntu 18.04
    steps:
      - uses: actions/checkout@v2
      - run: yarn install
      - run: yarn build:dumi
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: The ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist
Copy the code

preview

Now we can visit youngjuning.js.org/vant-react-… Check the effect:

Configuration optimization

Now the Dumi-based documentation site is just initialized, and many configurations (.umirc.ts) can be optimized, such as:

  1. Configure CDN acceleration based on JSDeliver
const isProd = process.env.NODE_ENV === 'production'; .publicPath: isProd ? 'https://cdn.jsdelivr.net/gh/youngjuning/vant-react-native@gh-pages/': '/'.Copy the code
  1. Incremental publishing and avoiding browser load caching
{
  hash: true
}
Copy the code
  1. Amun website statistics
{
  scripts: ['https://s9.cnzz.com/z_stat.php?id=1280093214&web_id=1280093214'].styles: ['a[title= webmaster statistics] {display: none; } '],}Copy the code
  1. configurationexportStatic: {}Output all routes as AN HTML directory structure to avoid 404 when the page is refreshed.

Previews the Pull Request

Consider that the community will contribute code and documentation later. Before PR is incorporated into the main branch, we need to preview the document or component. The solution is a statically managed service called Surge. Sh, which allows you to publish HTML, CSS, and JS files to the Web for free with simple commands from the command line.

To apply for the firm Token

Installing the SURGE CLI:

npm install --global surge
Copy the code

Register for surge:

suerge login
Copy the code

Access token:

suerge token
Copy the code

Configure a CI

Due to GitHub security issues, the Surge – Preview Action plug-in is not available. We have customized the CI based on dumi’s official configuration. First, we copy the three files shown below into the project.

Then modify build step in preview-build.yml:

- NODE_OPTIONS='--max-old-space-size=4096' yarn build
+ NODE_OPTIONS='--max-old-space-size=4096' PREVIEW_PR=true yarn build:dumi
Copy the code

The environment variable PREVIEW_PR=true is added to enable Dumi to recognize that the package is not in production, so.umirc.ts needs to be modified accordingly:

const isProd =
  process.env.NODE_ENV === 'production'&& process.env.PREVIEW_PR ! = ="true"; .publicPath: isProd ? 'https://cdn.jsdelivr.net/gh/youngjuning/vant-react-native@gh-pages/': '/'.Copy the code

Then, change the deployment domain name dumi-preview in the preview-deploy.yml file to vant-react-native-preview.

Finally, we add the previous Surge Token to the Secrets of the warehouse.

Results show

PR preview status being deployed:

Deployment success status:

Access vant-react-native-preview-pr-1. Surge. Sh/to verify the validity of the document ✅.

Unit testing

I in the React with Jest and Enzyme Native unit test | technical review The article was submitted unit testing and documentation, is one of the most important aspects of the quality of the smallest unit security program. Admittedly, a mature component library is bound to have unit testing. This chapter will not expand on unit testing, but focus on how vant-React-Native unit tests are configured.

Install dependencies

Jest, babel-jest, @types/jest dependencies are already installed, so we need to install enzyme, a jEST-based unit testing framework.

$ yarn add enzyme jest-enzyme enzyme-adapter-react-16 enzyme-to-json @types/enzyme react-native-mock-render -DW
Copy the code

Enzyme is a JavaScript test utility for React that makes it easier to test the output of the React component. You can also work on the output given, traversing and simulating the runtime in some way.

configuration

Jest. Config. Js:

module.exports = {
  preset: 'react-native'.verbose: true.collectCoverage: true.// Generate a test coverage report
  moduleNameMapper: {
    // for https://github.com/facebook/jest/issues/919
    '^image! [a-zA-Z0-9$_-]+$': 'GlobalImageStub'.'^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': 'RelativeImageStub',},setupFilesAfterEnv: ['<rootDir>/jest.setup.js']./ / Jest run the setup file is used to configure the Enzyme and adapter (shown in Jest below. Setup. Js), before is setupTestFrameworkScriptFile, can also use the setupFiles
  snapshotSerializers: ['enzyme-to-json/serializer'].// It is recommended to use the serializer to use the enzyme to json, which is very simple to install and use and allows you to write concise snapshot tests.
};
Copy the code

Jest. Setup. Js:

import 'react-native';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });
Copy the code

A simple example:

// packages/button/__test__/index.tsx
import React from 'react';
import { shallow } from 'enzyme';
import Button from '.. /src/index';

function setup(props = {}) {
  const wrapper = shallow(<Button />);
  const instance = wrapper.instance();
  return { wrapper, instance };
}

describe('Button Component'.() = > {
  it('renders correctly'.() = > {
    const { wrapper } = setup();
    expect(wrapper).toMatchSnapshot();
  });
});
Copy the code

After you run the jest command, you can view the following coverage:

For the mighty

Can write long is not a warrior, can persist to see here is a warrior. Thank you for reading. However, component library engineering is just a starting point. If this article is well received, the specific component design and implementation of the component library, the complete React Native unit testing tutorial, and so on will be covered in luo Chu’s subsequent articles.

Recommended UI library

Of course, vant-React-Native isn’t your only option. The following UI libraries are excellent projects. When implementing Vant-React-Native, I also borrowed some excellent designs from predecessors.

  • antd-mobile-rn
  • react-native-elements
  • react-native-ui-kitten
  • react-native-ui-lib
  • Zarm