In daily development, we have long been tired of capricious code code, there is always a restless heart, their whole one to play, although the existing component library on the market has been very perfect, but still want to experience a sense of accomplishment with their own component library, along with also improve their own!

Technology: Typescript create-React-app Sass Jest Storybook Classnames Husky Fontawesome

Create-react-app construction project

Common template: NPX create-react-app my-app CD my-app NPM startCopy the code
Typescript template: NPX create-react-app my-app --template typescriptCopy the code

Planning a Directory Structure

├.storyBook ├ main.js │ ├ yourTheme. Js │ ├ YourManager.js │ ├ yourTheme. Js │ ├ yourTheme. Change the top left corner logo ├. Vscode Vscode Config ├ Public Public File ├ SRC source ├ Components ├ Button... ├ Stories Storybook file ├ Button.stories.tsx Example: Button component to preview stories.tsx │ ├ styles │ ├ _React.scSS │ in │ ├ _react.scSS │ Type reset.css ├ _Varialbes. SCSS Global style variable definition ├ Inde. SCSS All style files introduced along with management ├ Index. TSX entry ├.gitignore Git ├ Package. json package │ ├ tsConfig.build. Json Component │ ├ Readme. mdCopy the code

SCSS

  • Determine the global style, the subsequent use of direct use in the form of variables, easy to manage
_variables.scssThe basic color$white:    #fff! default;$gray-100: #f8f9fa! default;$gray-200: #e9ecef! default;$gray-300: #dee2e6! default;$gray-400: #ced4da! default;$gray-500: #adb5bd! default;$gray-600: #6c757d! default;$gray-700: # 495057! default;$gray-800: #343a40! default;$gray-900: # 212529! default;$black:    # 000! default; The theme color$blue:    #0d6efd! default;$indigo:  #6610f2! default;$purple:  #6f42c1! default;$pink:    #d63384! default;$red:     #dc3545! default;$orange:  #fd7e14! default;$yellow:  #fadb14! default;$green:   #52c41a! default;$teal:    #20c997! default;$cyan:    #17a2b8! default;// Font size
$font-size-base:              1rem! default;// Assumes the browser default, typically `16px`
$font-size-lg:                $font-size-base * 1.25! default;$font-size-sm:                $font-size-base*.875! default;$font-size-root: null ! default; .Copy the code
  • Global normalize. CSS
  • Global minxin
define@mixin button-size($padding-y.$padding-x.$font-size.$border-radius) {
  padding: $padding-y $padding-x;
  font-size: $font-size;
  border-radius: $border-radius; } use@include button-size($btn-padding-y.$btn-padding-x.$btn-font-size.$border-radius);
Copy the code
  • Style integration Management

Note that when the file name defines the reference to _xxxx. SCSS, you can omit _

// config
@import "variables";

//layout
@import "reboot";

//mixin
@import "mixin";

// animation
@import "animation";

/ / button style
@import ".. /components/Button/style";

// icon
@import ".. /components/Icon/style";

/ / the menu style
@import ".. /components/Menu/style";

/ / input pattern
@import ".. /components/Input/style";

//upload
@import ".. /components/Upload/style";

//progress
@import ".. /components/Progress/style";
Copy the code

Of course, there are other advanced functions, you can learn by yourself

Components are written

File layout

├ Button ├ _style. SCSS component Style ├ Button.tsX component source ├ button.test.TSX component unit Test ├ Index.tsX component export button.tsX sample/* * @Descripttion: * @version: * @Author: zoucw ([email protected]) * @Date: 2021-02-14 13:54:04 * @LastEditors: Please set LastEditors * @LastEditTime: 2021-02-17 16:42:36 */

import React from 'react';
import classNames from 'classnames';

export type ButtonSize = 'lg' | 'sm'
export type ButtonType = 'primary' | 'default' | 'danger' | 'link'interface BaseButtonProps { className? : string;/** Set Button disable */disabled? : boolean;/** Sets the Button size */size? : ButtonSize;/** Sets the Button type */btnType? : ButtonType; children: React.ReactNode; href? : string; } type NativeButtonProps = BaseButtonProps & React.ButtonHTMLAttributes<HTMLElement> type AnchorButtonProps = BaseButtonProps & React.AnchorHTMLAttributes<HTMLElement>// Partial makes all attributes optional
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>

const Button: React.FC<ButtonProps> = (props) = > {
  const{ btnType, className, disabled, size, children, href, ... restProps } = props;// btn btn-lg btn-primary btn-default
  const classes = classNames('btn', className, {
    [`btn-${btnType}`]: btnType,
    [`btn-${size}`]: size,
    'disabled': (btnType === 'link') && disabled
  })
  if (btnType === 'link' && href) {
    return (
      <a
        className={classes}
        href={href}
        {. restProps}
      >
        {children}
      </a>)}else {
    return (
      <button
        className={classes}
        disabled={disabled}
        {. restProps}
      >
        {children}
      </button>
    )
  }
}

Button.defaultProps = {
  disabled: false.btnType: 'default'
}

export default Button
Copy the code

Component preview debugging

We recommend a UI component preview tool that you can manually configure to show the component test component and also show the source code of the component. It is called Storybook

I won’t go into too much detail here, it’s easy to install and run, and some of the other custom display features should be studied carefully

The installation

npx sb init

npm run storybook
Copy the code

After NPX sb init is done in your project root, storybook will automatically add a. Storybook folder to your project root. Create a Stories folder at the level of your Component folder

New Component preview STORIES -> xxx.stories.tsx -> Introduce your own component

After configuration, the style will look like this. Many styles can be customized

Compilation before release

  • The first is to compile the TSX file into JS
"scripts": {..."build-ts": "tsc -p tsconfig.build.json",
}

tsconfig.build.json
{
  "compilerOptions": {
    "outDir": "dist"."module": "esnext"."target": "es5"."declaration": true."jsx": "react"."moduleResolution":"Node"."allowSyntheticDefaultImports": true,},"include": [
    "src"]."exclude": [
    "src/**/*.test.tsx"."src/**/*.stories.tsx"."src/setupTests.ts"."src/reportWebVitals.ts"]}Copy the code
  • Compile SCSS
"scripts": {
    "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",}Copy the code

Once compiled, the following files are built

Note: While we are developing components, how can we test them by iterating over functionality introduced into production?

1. Complete the package.json configuration

{
  "main": "dist/index.js"."module": "dist/index.js"."types": "dist/index.d.ts",}Copy the code

2. Run NPM link in the root directory of the component project

3. Switch to the root of the test project and execute NPM link name (note: this name is the name in your component project package.json).

4. Open node_modules of the test project and check whether there is a dependency package named name with an icon at the end

5. Use it as a component in your test project (use the NPM installation package as it is)

Another error occurs when you test certain components: ❌Inside the imported componentimportthereactWith the test itemreactLink UI component project react to the current test project

Example NPM link.. /cute-spring/node_modules/reactCopy the code

Do you want to reference the NPM package at this point? No, please keep reading

Npm release

1. Have you registered an NPM account (if not, click Register)

2. Continue to refine the package.json configuration items, as NPM displays the package information from the package.json configuration

{..."description": "this is my component".// Configure the entry file
    "main": "dist/index.js"."module": "dist/index.js"."types": "dist/index.d.ts"."author": "xxx"."private": false."license": "MIT"."keywords": [
      "Component"."UI"."React"]."homepage": "https://github.com/Spring-Listening/cute"."repository": {
      "type": "git"."url": "https://github.com/Spring-Listening/cute"
    },
    // This configuration is key because 'NPM publish' uploads two files, one in the user-defined files and one in the default uploaded file, such as readme.md package.json
    "files": [
      "dist"],... }Copy the code

3. Solve the problem that NPM package conflicts with the react component project

Package Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies Dependencies

"peerDependencies": {
  "react": "> = 16.8.0"."react-dom": "> = 16.8.0"
},
Copy the code

Before installing this UI component, your development project must install a dependency package that meets the above requirements, so that components in the component library can use the dependencies of the development project, so that the component library does not have to install duplicate dependencies, and does not have the above symptoms

4. Polish engineering stuff, such as ESLint component unit test Husky

{
  "scripts": {
    // eslint
    "lint": "eslint --ext js,ts,tsx src --max-warnings 5".// Unit tests
    "test:nowatch": "cross-env CI=true react-scripts test".// Compile the TS SCSS file
    "build": "npm run clean && npm run build-ts && npm run build-css".// NPM publish executes this command and uploads the built file to the NPM server
    "prepare": "npm run build",},"husky": {
    "hooks": {
      "pre-commit": "npm run test:nowatch && npm run lint"}}},Copy the code

Perform NPM publish

Life is not always smooth, you may encounter various 403 error caused upload failure

Don't lose heart when you encounter difficulties, follow this checklist one by one: 1. Check if you are logged in to NPM (NPM whoami) 2. Have you mixed up multiple accounts and logged out again? 3. Has the email address you filled in when registering been verified? Check if your package name is already in use 5. Check if your version number is already in use 6. Check if your NPM source is an official NPM source (NPM config list)Copy the code

JestUnit testing

In daily work, the general business may not engage in this unit test. On the one hand, the business changes frequently, and the test cases can not be reused, resulting in low utilization rate of test cases, resulting in low efficiency. Second, test users will also play a certain discount on efficiency, not in line with the face of capitalists today. However, for this type of component development, the business logic is stable, and it is not easy to completely overturn the redo, so it is better suited for unit testing.

The test dependency package is mainly divided into three parts: Jest, jest-DOM, @testing-library/ React (or any other framework package) Jest is the core package of this test. It provides an API for basic functions, because in daily development, dom aspects are involved. The jest-dom package mainly provides THE DOM-related API, and the @testing-library package mainly provides the testing API of the most commonly used frameworks in the market

These are some of the basic apis that Jest provides

ToBe () is used to check whether the values of basic data types are equal. ToEqual () is used to check the values of reference data types. Due to the nature of the JS object data type, reference data type comparison is only a comparison of Pointers, but it requires comparison of each value of the object. So the toEqual() Truthiness matcher toBeNull only matchesnullToBeUndefined matches onlyundefinedToBeDefined is the opposite of toBeUndefined toBeTruthy matches anythingifStatement for true toBeFalsy matches anyifToBeGreaterThan is greater than or equal to toBeGreaterThanOrEqual toBeLessThan is less than toBeLessThanOrEqual Tobe and toEqual are equivalent. The function is the same for numeric toMatch string matchers and string matches. ToContain array matchers are used to determine if an array contains any values Used to test for a specific throw error, either by determining the text of the error statement (which supports regular matching) or by determining the type of error.Copy the code

Asynchronous test examples

// Learn about async and await usage methods and scenarios in advance

test('the data is peanut butter'.async() = > {// Remember to add assertion tests
  expect.assertions(1);
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});
Copy the code

@testing-library/react

import {render, fireEvent} from '@testing-library/react'

test('should render the correct default button'.() = > {
  const warpper = render(<Button {. defaultProps} >Nice</Button>)
  const element = warpper.getByText('Nice') as HTMLButtonElement
  expect(element).toBeInTheDocument()
  expect(element.tagName).toEqual('BUTTON')
  expect(element).toHaveClass('btn btn-default')
  expect(element.disabled).toBeFalsy()
  // Simulate the click event
  fireEvent.click(element)
  expect(defaultProps.onClick).toHaveBeenCalled()
})
Copy the code

React test examples @testing-library/react

jest-dom

The semantics of these DOM related apis are obvious. You can see the API name to know what the functionality is. For details, see the documentation github.com/testing-lib…

CI CD continuous integration, continuous deployment

After component development, how to send our component document to the Internet, and is a key release, do not have to send manually, tedious! Let’s optimize the process

  • Open the Travis www.travis-ci.com/
  • Log in with the GitHub license and select the component library to Travis
  • Adding a file to the project does not automatically trigger the process without it
.travis.yml
/ / language
language: node_js
/ / the node version
node_js:
  - "stable"
cache:
  directories:
  - node_modules
/ / environment
env:
  - CI=true
script:
// Build command
  - npm run build-storybook
Send the package to 'GitHub's' pages'
deploy:
  provider: pages
  skip_cleanup: true
  // Configure the token key
  github_token: $github_token
  // The folder name of the built package
  local_dir: storybook-static
  on:
    branch: master
Copy the code

Configuration making - token

GitHub -> Click on the avatar drop-down arrow -> Settings -> Developer Settings -> Personal Access Tokens -> Generate new TokensYou need to check the permissions of the token before generating it, and click the button to create it, as shown below

The token was created successfully. Be sure to copy it

Travis configures the token that was just generated

In the upper right corner of the home page, choose More Options > Settings With everything configured, the code is submitted and the CICD process is triggeredHow does a successful trigger access the package we sent

Spring – listening. Making. IO/package name /? path=/

The end

The paper come zhongjue shallow, and must know this to practice

No matter how well you study other people’s documents, there will always be problems with practice, so do it

There is insufficient place hope each big guy complement, common progress, everyone is really good!

Component source