“This is the second day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.

preface

I used storybook to try to build a set of component library “Storybook from Zero to One Building component Library (Part 1)”, but after a whole operation, I found that the learning cost of Storybook is relatively high and the configuration is complex, and the most important thing is that the page is ugly. Then I started to use Dumi, and found that it was really fragrant. It was not only easy to get started, just like normal development of an ordinary project, but also did not have complicated configuration. The most important thing was that the page was beautiful, simple and generous. A component library like ANTD that you deserve.

Learn how to build a React+TS component library, write a complete component, deploy it to the Github&Gitee static Web site, and publish the NPM public package.

Technology used: Dumi: responsible for component development and component document generation (based on UMI, students who have used UMI are friendly and easy to use) Github: configure automatic deployment static Web Gitee: synchronize Github GH Pages

Features:

  • 📦 Out of the box, focus on component development and documentation
  • 📋 rich Markdown extension, beyond rendering component demo
  • 🏷 automatically generates component apis based on TypeScript type definitions
  • The 🎨 theme is easy to customize and you can also create your own Markdown components
  • 📱 support mobile component library development, built-in mobile hd rendering scheme
  • 📡 one line of command datafies component assets and concatenates them with downstream productivity tools

Environment to prepare

  • node: v10.13.0 or later versions

The installation

Build a library of site-pattern components

# $yarn create @umijs/dumi-lib --site # $yarn create @umijs/dumi-lib --siteCopy the code

The project directory structure is roughly as follows:Install dependencies, start up, and you’ll see a front page similar to the official website and a concise component page.Home page:

The home page corresponds to root/docs/index.md in the project

Component page:

The component page corresponds to root/ SRC /index.ts in the project

configuration

Mainly navigation and menu configuration:.umirc.ts

import { defineConfig } from 'dumi'; function getMenus(opts: { lang? : string; Base: '/ components' |'/docs' {}) const menus = {'/docs: [{title: 'Introduce', 'title_zh - CN' : 'introduction, the path: '/ docs/guide,}, {title:' FAQ ', 'title_zh - CN' : 'the problem', path: '/ docs/FAQ'},], '/ components: [{title: 'Common', 'title_zh-cn ': 'Common', children: ['/components/button', '/components/icon', '/components/typography'], }, { title: 'Layout', 'title_zh-CN': 'Layout ', children: [ '/components/layout/Divider', '/components/layout/Grid', '/components/layout/Layout', '/components/space', ], }, ], };  return (menus[opts.base] as [])? .map((menu: any) => { if (! opts.lang) return menu; return { ... menu, title: menu[`title_${opts.lang}`] || menu.title, }; }); } export default defineConfig({ title: 'fish-ui', hash: true, base: '/fish-ui', publicPath: '/fish-ui/', favicon: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg', logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg', outputPath: 'docs-dist', mode: 'site', mfsu: {}, dynamicImport: {}, navs: [/ / null, {title: 'document' path: '/ docs'}, {title:' components' path: '/components', }, { title: 'GitHub', path: 'https://github.com/yingliyu/fish-ui', }, ], menus: { '/zh-CN/docs': getMenus({ lang: 'zh-CN', base: '/docs' }), '/docs': getMenus({ base: '/docs' }), '/zh-CN/components': getMenus({ lang: 'zh-CN', base: '/components' }), '/components': getMenus({ base: '/components' }), }, // more config: https://d.umijs.org/config lessLoader: {javascriptEnabled: true}, / / on-demand loaded antd extraBabelPlugins: [ [ 'babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es', style: true, }, ], ], });Copy the code

Menus and NAVs in this configuration correspond to the following page layout:

Completing a component

Based on the Ant Design Button component, for example: / components/Button/index. The TSX components (write)

import React from 'react'; import { Button as AntdButton } from 'antd'; import classNames from 'classnames'; import './index.less'; declare type ButtonHTMLType = 'submit' | 'button' | 'reset'; declare const ButtonTypes: ['default', 'primary', 'ghost', 'dashed', 'link', 'text']; export declare type ButtonType = typeof ButtonTypes[number]; interface IABSButtonProps { loading? : boolean; danger? : boolean; className? : string; type? : ButtonType; style? : React.CSSProperties; icon? : React.ReactNode; children? : React.ReactNode; disabled? : boolean; block? : boolean; large? : boolean; htmlType? : ButtonHTMLType; color? : 'blue' | 'red' | 'yellow' | 'green' | 'white'; onClick? : React.MouseEventHandler<HTMLButtonElement>; } const Button: React.FC<IABSButtonProps> = (props: IABSButtonProps) => { const { danger, loading, style, children, large = false, className, onClick, type = 'primary', icon, block = false, disabled, htmlType, color, } = props; let classes = classNames('fish-btn', className, { 'fish-btn-large': large, 'fish-btn-block': block, 'fish-btn-link': type === 'link', }); const displayStyle = block ? 'block' : 'inline-block'; let newTpye = type; let isDanger = danger; if (color) { switch (color) { case 'blue': newTpye = 'primary'; break; case 'red': isDanger = true; break; case 'white': newTpye = 'default'; break; case 'yellow': classes = classNames(classes, 'fish-yellow-btn'); break; case 'green': classes = classNames(classes, 'fish-green-btn'); break; default: break; } } return ( <div className={classes} style={{ display: displayStyle, ... style }}> <AntdButton icon={icon} onClick={onClick} disabled={disabled} type={newTpye} block={block} htmlType={htmlType}  danger={isDanger} loading={loading} > {children} </AntdButton> </div> ); }; export default Button;Copy the code

/ components/button/index. Less (component style)

.color(@bg, @bgHover, @bgActive) when (default()) { .ant-btn { color: @text-color-inverse; background: @bg; border-color: @bg; &:hover, &:focus { color: @text-color-inverse; background: @bgHover; border-color: @bgHover; } &:active { color: @text-color-inverse; background: @bgActive; border-color: @bgActive; } } } .fish-btn { &.fish-btn-block { width: 200px; } &.fish-btn-link { .ant-btn-link { padding: 0; } } &.fish-yellow-btn { .color(@warning-color, @gold-5, @gold-7); } &.fish-green-btn { .color(@success-color, @green-5, @green-7); }}Copy the code

/ components/button/index. The md (component documentation)

-- Title: Button group: PATH: / Components Order: 1 -- ## Button responds to user click behavior and triggers corresponding business logic. TSX import React from 'React '; import { Button, Space } from 'fish-ui'; export default () => ( <Space> <Button>Button</Button> <Button danger>Button</Button> <Button large>Button</Button> </Space> );Copy the code

The button color

import React from 'react';
import { Button, Space } from 'fish-ui';

export default() = > (<Space>
    <Button color="white">Button</Button>
    <Button color="blue">Button</Button>
    <Button color="red">Button</Button>
    <Button color="yellow">Button</Button>
    <Button color="green">Button</Button>
    <Button>Button</Button>
  </Space>
);
Copy the code

API

Page display:

So far, the antD-based Button component and documentation have been written in embryonic form, but a very important part of the API of the document has not been added. Using MD syntax to write API is tedious and violates the original intention of focusing on component development. Wouldn’t it be nice if our TS API could be generated automatically based on type declarations and code comments, and yes it can, via JS Doc annotations + TypeScript type definitions.

Automatic generation API

Prerequisite: Make sure Dumi is able to derive API content from TypeScript type definitions + annotations. The type resolution tool behind Dumi is react-Docgen-typescript. See its documentation for more types and annotations

The installation

npm install --save-dev react-docgen-typescript
Copy the code

configuration

The project root directory creates the configuration file styleguide.config.js

module.exports = {
  propsParser: require('react-docgen-typescript').withDefaultConfig([parserOptions]).parse,
};
Copy the code

Modifying component code

The components/button/index. The TSX add comments as follows:

Interface IABSButtonProps {/** Set button loading state */ loading? : boolean; /** * set danger button * @default false */ danger? : boolean; className? : string; /** Button type */ type? : ButtonType; style? : React.CSSProperties; /** Set button icon component */ icon? : React.ReactNode; children? : React.ReactNode; /** * Button invalid status * @default false */ disabled? : boolean; block? : boolean; large? : boolean; htmlType? : ButtonHTMLType; /** button color */ color? : 'blue' | 'red' | 'yellow' | 'green' | 'white'; onClick? : React.MouseEventHandler<HTMLButtonElement>; }Copy the code

Components/button/index. The md in the location you want to display the API page reference API

<API></API>
Copy the code

Results show

Based on the above code, the following API table is automatically generated:

Automated deployment

The component library is automatically deployed on Github GH-Pages. Since Github is slow to access, a copy of the component library is also deployed on Gitee GH-Pages.

For example, to access my component libraries: yingliyu.github. IO /fish-ui, ylyubook.giitee. IO /fish-ui need to be modified if not deployed in the root directory.

// .umirc.ts
export default defineConfig({
  base: '/fish-ui',
  publicPath: '/fish-ui/'
  ...
})
Copy the code

Github /workflows/gh-pages. Yml was created in the project root directory

Name: Deploy Github Pages # Actions name on: # Trigger condition push: Branches: -master # Trigger jobs: build: # job id name: Build and publish # job id run-on: Ubuntu - Latest # runtime environment Optional ubuntu-latest, ubuntu-18.04, ubuntu-16.04, Windows-latest, Windows-2019, Windows-2016, MacOS-latest, Macos-10.14 Steps: - uses: actions/checkout@v2 - name: Use node.js 14.x uses: actions/setup-node@v2 with: node-version: 14.x - name: Setup env run: | npm install - name: Generate public files run: | NPM run docs: build # release to making pages - name: Auto Deploy env: GH_REF: ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}} # github token GITEE_REF: Git # gitee usename = GITEE_TOKEN = GITEE_TOKEN = GITEE_TOKEN = gitee usename = GITEE_TOKEN ${{secrets.GITEE_TOKEN}} # gitee private token run: | git config --global user.name "your name" git config --global user.email "your email" git clone https://${GH_REF} .deploy_git cd .deploy_git git checkout gh-pages cd .. / mv.deploy_git /.git/./docs-dist # docs-dist CD./docs-dist git add. Git commit -m ":construction_worker:CI built at `date +"%Y-%m-%d %H:%M:%S"`" # GitHub Pages git push --force --quiet "https://${ACCESS_TOKEN}@${GH_REF}" gh-pages:gh-pages # Gitee Pages git push --force --quiet "https://[gitee usename]:${GITEE_TOKEN}@${GITEE_REF}" gh-pages:gh-pagesCopy the code

The above GITEE_TOKEN is a private token created in Gitee. Here we obtain it from Gitee. The specific address is gitee.com/profile/per… . Generate and copy tokens and add them to the corresponding Github repository. ACCESS_TOKEN is Github Secrets.

Release NPM package

Note: NPM package is completely released public, that is all the people who use NPM can be downloaded in NPM warehouse you release the package, but the actual projects, the department between utility package may involve the business secret, so can’t released on the NPM, the company needs to set up their own private package management, warehouse, then can use CNPM.

This paper only records the steps of NPM package release and the construction of CNPM private warehouse. Please click here for reference. Before publishing, you need to configure package.json to add the necessary description information.

//package.json
 "private": false."name": "fish-ui-pro"."version": "1.0.0"."description": "A library of react components"."author": "yingliyu"."license": "MIT"."keywords": [
    "React"."Component"]."homepage": "https://yingliyu.github.io/fish-ui"."repository": {
    "type": "git"."url": "https://github.com/yingliyu/fish-ui.git"
  },
   "files": [
    "docs-dist"."es"].Copy the code
  • willprivateSet the field to false to indicate a non-private package.
  • adddescriptionauthor ,licensekeywordsAnd other related fields;
  • addhomepageField, the project home page URL;
  • addrepositoryField, that is, project warehouse address URL;
  • Add a files field that indicates which files to upload to NPM. If you write nothing, the default is.gitignore The information inside. But be careful, no matter .gitignoreHow to configure that some files will always be published topackageThese documents includepackage.json ,README.md ,LICENSE And so on;
/ / package. Json "peerDependencies" : {" react ":" > = 16.9.0 ", "the react - dom" : "> = 16.9.0", "antd" : "> = 4.18.0"},Copy the code

Run NPM login, enter username, password, and email for NPM registration, and run NPM whoami. If the username is displayed, the login is successful. Finally, run NPM publish and send packets. Note: Build before publish to make sure the DIST package is up to date.

Error: 403: NPM package name already used

The last

This is the end of the task, congratulations on your own component library and NPM package!!

Reference: Dumi official website, juejin.cn/post/684490…

Copyright belongs to author Fan Xue, all rights reserved.