preface

As the saying goes: “sparrow is small, all the five organs”, build a component library, know the criticism, not easy to go, involved in all aspects of technology, just like the sea is calm, but actually reefs and rapids, dangerous everywhere ~

At present, the team has mature NutUI component library of Vue technology stack [1] and Yep – React component library of React technology stack [2]. However, most of these component libraries were built from scratch, including the complex configuration of Webpack, the development of Markdown file to Vue file function, the development of unit test function, the development of Babel plug-in loaded on demand and so on. It is not easy to complete the whole component library project, but also a huge project. If we want to quickly build a component library, it is not necessary to spend so much energy, we can use the relevant professional library in the industry, through assembly and debugging, quickly achieve a component library. Using the create-React-app scaffolding, docZ document generator, Node-sass, and Netlify deployment project to develop the entire component library, this article introduces the process of using create-React-App scaffolding, DOCZ document generator, Node-sass, and Netlify deployment project to develop the whole component library. Let’s get down to business:

Let’s take a look at the final result of the component library:

This article describes how to build a React component library:

Build a local development environment

Create – React -app creates – React -app, creates – React -app, creates – React -app, creates – React -app, creates – React -app, creates – React -app, creates – React -app, creates – React -app, creates – React -app, creates – React -app

Initialize scaffolding with create-react-app and install TypeScript

npx create-react-app myapp --typescript

Note The node version is later than 10.15.0

2. Configure ESLint for formatting

Env configuration file in the project root directory. Set EXTEND_ESLINT=true to enable ESLint detection. Be sure to restart vscode

3. Component library system file structure

Create a new styles folder that contains the basic style files as follows:

|-styles
| |-variables.scss // Various variables and configurable Settings
| |-mixins.scss    / / global mixins
| |-index.scss    // Import the entire SCSS file and throw out the style entry
|-components
| |-Button
|   |-button.scss // A separate style for the component
|   |-button.mdx // The documentation for the component
|   |-button.tsx // The core code of the component
|   |-button.test.tsx // Unit test file for the component
|  |-index.tsx  // Component external entry
Copy the code

4. Install the Node-sass processor

Install node-sass to compile the SCSS style file: NPM I node-sass -d

Now that the react development environment is basic, you can have fun developing components.

Second, component library package compilation

After debugging the component library locally, we need to package and compress the compiled code for other users to use. Here we are writing in TypeScript, so we compile the project in TypeScript by creating a new index.tsx file in each component:

import Button from './button'
export default Button 
Copy the code

Modify the index. TSX file, import and export each module

export { default as Button } from './components/Button'
Copy the code

Create a new tsconfig.build.json file in the root directory and compile the.tsx file:

{
  "compilerOptions": {
    "outDir": "dist".// Generate a directory
    "module": "esnext"./ / format
    "target": "es5"./ / version
    "declaration": true.// Generate.d.ts files for each ts file
    "jsx": "react"."moduleResolution":"Node".// Specify the node standard path for finding import files
    "allowSyntheticDefaultImports": true,},"include": [// Which files to compile
    "src"]."exclude": [// Exclude files that do not need compilation
    "src/**/*.test.tsx"."src/**/*.stories.tsx"."src/setupTests.ts"]},Copy the code

For style files, compile SCSS using Node-sass, extract all SCSS files to generate CSS files:

"script": {"build-css": "node-sass ./src/styles/index.scss ./dist/index.css",}Copy the code

And modify the build command:

"script": {"clean": "rimraf ./dist".// Cross-platform compatibility
    "build": "npm run clean && npm run build-ts && npm run build-css",}Copy the code

In this way, after the NPM Run build is executed, the corresponding component JS and CSS files can be generated for later users to load and deploy to NPM on demand.

3. Local debug component library

After the component library is developed locally, you need to debug it locally before publishing it to the NPM to avoid uploading it to the NPM with problems. This is where NPM link comes in.

What is NPM Link

In the local development of the NPM module, we can use the NPM link command to link the NPM module to the corresponding running project, convenient debugging and testing of the module.

Method of use

Assume that the component library is the Reactui folder and you want to use components in your local demo project. The NPM link is performed in the component library (where it is to be linked) and the mapping is generated from the local node_modules/reactui to the component library path /reactui. Then execute NPM Link Reactui in the folder demo where you want to use the component library to generate the following corresponding chain:

In the folder demo where you want to use the component -[map to] — > local node_modules/reactui — [map to]-> the folder /reactui of the development component library Reactui

The component library package.json file needs to be modified to set the entry:

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

Then add to the dependencies of the demo project that will use the component:

"dependencies": {"reactui":"0.0.1"
}
Copy the code

Note that the install dependency is not used at this point, but is written so that it can be used in projects with code hints. Then use this in the demo project:

import { Button } from 'reactui'
Copy the code

Introduce CSS files in index.tsx

import 'reactui/build/index.css'
Copy the code

Just when we thought we were done, the following error dropped like a shower of cold water:

After various troubleshooting problems, the react official website [3] found the following statement:

🔴 Do not call Hooks in class components.

🔴 Do not call in event handlers. 🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.

It was very clear:

Cause 1: Versions of React and React DOM are different cause 2: Hooks rules may be broken Cause 3: Multiple versions of React are used in the same project

The website is kind enough to offer a solution:

This problem can also come up when you use npm link or an equivalent. In that case, You bundler might “see” two Reacts — one in application Folder and one in your library Folder. Assuming myApp and Mylib are sibling folders, one possible fix is to run npm link .. /myapp/node_modules/react from mylib. This should make the library use the application’s react copy.

The core idea is to use NPM link mode in the component library, and introduce react in the demo project. So execute in the component library: NPM link.. /demo/node_modules/react

The specific steps are as follows:

  1. NPM link is implemented in the code base Reactui

  2. Execute NPM link.. in code base Reactui /.. /demo/node_modules/react

  3. Execute NPM Link Reactui in the project demo

React conflicts can be resolved. You can then have the fun of debugging the component library locally and seeing the end result in the project that uses the component.

Publish component libraries to NPM

This process must be careful to use NPM source!! [Very important]

First make sure you are logged in to NPM:

npm adduser
// Enter the user name; Password; email
npm whoami // View the current login name
Copy the code

Modify component library package.json, note files configuration; And simplification of dependencies file: The React dependencies dependencies are stored in devDependencies, but they might conflict with the react version installed by the user. If the user does not install React, the component library cannot be used. PeerDependencies are the dependencies that react and react-dom require:

"main": "dist/index.js"."module": "dist/index.js"."types": "dist/index.d.ts"."files": [ // Which files to upload to NPM
  "dist"]."dependencies": {  // These dependencies are installed in node_modules when NPM I is executed
  "axios": "^ 0.19.1." ".// Send the request
  "classnames": "^ 2.2.6." ".//
  "react-transition-group": "^ 4.3.0"
},
"peerDependencies": { // Important!! To remind the consumer of the core dependencies of the component library that must be installed before they can be used
  "react": "> = 16.8.0".// hooks were introduced after 16.8
  "react-dom": "> = 16.8.0"
}
Copy the code

Since the component library uses the create-React-app scaffolding, the latest version of the component library has integrated unit testing. Husky configuration and other specifications submitted, not described here, readers can configure their own.

5. Generate documentation

At present, storybook[4], DOCZ [5] and other good tools for generating explanatory documents are excellent tools for document generation, but they have their own disadvantages and advantages. After careful investigation and comparison, DOCZ was finally selected.

names The difference between a The difference between the two
storybook Develop documentation using a proprietary API that can introduce Markdown files The document-generating interface has more of a storybook touch
docz Perfect combination of React and Markdown syntax for developing documentation The resulting document interface is a regular document interface

1, determine the selection

1) Storybook’s common compiled document specification is a bit more verbose than DOCz’s

The compilation document specification for storybook looks like this:

// omit the code imported by import
storiesOf('Buttons'.module)
.addDecorator(storyFn= > <div style={{ textAlign: 'center' }}>{storyFn()}</div>)
.add('with text'.() = > (
<Button onClick={action('clicked')} >Hello Button111</Button>), {notes:{markdown}   // Markdown content will be rendered
}) 
Copy the code

Compare the docZ development documents:

# Button componentImport {Playground, Props} from 'docz'; import Button from './index.tsx';## button component

<Playground>
 
** Basic attributes **The | | attribute names that default value | | | -- - | -- - | -- - | | btnType | -- - | | button typeCopy the code

Markdown is known as a lightweight markup language that allows people to write documents in plain text formats that are easy to read and write. When developing documents, team members skillfully use Markdown syntax, develop MDX files of DOCZ documents, and combine Markdown and React syntax. Compared with storybook, they need to use more APIS to write documents. This definitely reduces the cost of learning storybook syntax.

2) The document style generated by DOCZ is more in line with personal aesthetics

The document style generated by storybook, with a more serious storybook trace, looks like this:

The document diagram generated by DOCZ is as follows:

As can be seen from the above comparison, the interface generated by DOCZ is more brief and conventional. To sum up, I chose DOCZ based on the default document development habits and interface style. Of course, it depends on your opinion, and readers can also use storybook, which is also excellent

2. Use DOCZ for development

After determining the docZ for development, according to the official website, it is installed and configured in the component library generated by create-react-app:

npm install docz

After a successful installation, the following configuration is added to the package.json file

{
  "scripts": {
    "docz:dev": "docz dev"."docz:build": "docz build"."docz:serve": "docz build && docz serve"}}Copy the code

Doczrc. js file in the root directory of the project to configure docz:

export default {
  files: ['./src/components/**/*.mdx'.'./src/*.mdx'].dest: 'docsite'.// Package the docz document into the folder
  title: 'Top left header of component library'.// Set the document title
  typescript: true.// Support typescript syntax
  themesDir: 'theme'.// Which folder to put the theme styles in, will be explained later
  menu: ['Get started'.'Business Component'] // Generate the left menu category of the document
}
Copy the code

Files specifies which files docZ should compile to generate documents. If no restriction is made, it will search all files with the suffix of MD and MDX in the project to generate documents. Therefore, I made scope limitation in this file to avoid some readme. md files being generated into the document.

There are also two points to note:

1. Menu: [‘ get started ‘, ‘Business component ‘] corresponds to the menu bar category on the left of the component library. For example, in the MDX document, set the menu: Business component to the top of the component, then the Button component belongs to the category of “business component” :

---
name: Button
route: /button
Menu: service component --
Copy the code

Create a welcome page in SRC, set the route as the following path, and set the owning menu to “Get Started”.

-- name: Get started quicklyroute: /
---
Copy the code

NPM run docz:dev to open

Introduced here, it is estimated that a small partner will have questions, so the generated site is the same, can follow one’s desire to customize the style and function of the site? At the beginning, I also have this kind of question, after many attempts, god pays off, finally found the following method:

1. Modify the style of the DOCZ document itself

According to the method of adding logo in the official docZ document [6], the form of original components can be overwritten by custom components:

Example: If you’re using our gatsby-theme-docz which has a Header component located at src/components/Header/index.js you can override the component by creating src/gatsby-theme-docz/components/Header/index.js. Cool right?

So according to the docz source code theme part of the code: https://github.com/doczjs/docz/tree/master/core/gatsby-theme-docz/src, find the corresponding code structure of the document components, the component library project root directory with the name of the new folder:

|-theme
|  |-gatsby-theme-docz
|     |-components
|     |-Header
|       |-index.js // Modify the custom document component here
|       |-styles.js // Modify the generated style file here
Copy the code

In this way, when NPM run docz:dev is executed, the custom code overwrites the original style, realizing the diversification of the document.

2. Modify the Markdown document style

Is that the end of the matter? No! Our goal is not only that, because I find that the automatically generated Markdown format does not meet my aesthetic taste. For example, the generated table text is left aligned, and the whole table has a single style. However, this belongs to the category of Markdown style, and the code here is not included in the modification of the above document component. So how do you change the style of the markDown generated document?

After a brainwave and move, I found that in the above modify document component style, rewrite the component/headers/styles. The js file, whether can be the introduction of a custom style in this file? The file structure is as follows:

|-theme
|  |-gatsby-theme-docz
|     |-components
|     |-Header
|       |-index.js // Modify the custom document component here
|       |-styles.js // Modify the generated style file here
|       |-base.css  // Change the style of the markdown generated document here
Copy the code

The modified table looks like this:

Then you can customize the style of the document according to your aesthetic or visual design requirements.

Deploy documents to the server

It is meaningless to display the generated component library document only locally, so it needs to be deployed to the server, so the first thought is to put it on Github for hosting, open github setting option, github Pages set the branch of configuration:

In this case, the default home page path is:

https://plusui.github.io/plusReact/

But actually the valid access address of the page is with the folder docsite path:

https://plusui.github.io/plusReact/docsite/button/index.html

In addition, other resource paths introduced by the page are absolute paths, as shown in the following figure:

So putting packaged resources on Github doesn’t allow you to access them. At this time, we had to deploy the website to the cloud server. Considering the tedious server configuration, here is a simple deployment website: Netlify[7]

Netlify is a static web hosting service that provides CI services and can host Jekyll, Hexo, Hugo and other static web sites on GitHub, GitLab, etc.

The process of deploying the project is also simple, with a foolproof click to select the github code path and configure the folder and path, as shown below:

You can then click on the generated url to visit the deployed website:

And conveniently, Netlify automatically updates the site once it has deployed and then submits code to the codebase again. In addition, if you want to customize the URL, then you can only apply for the domain name, in their own cloud server, the resolution of the domain name. The configuration steps are as follows:

1) First of all, select Custom Domains under the Domain Settings of the component library on the Netlify website and add your own Domain name:

2) Then turn on the resolution Settings in domain name resolution in the cloud server and point the domain name to Netlify:

3) Finally open the set url, you can access the component library:

Vii. Components are loaded on demand

Ok, after the above flow, you can use the component library in the Demo project, but in the demo project, you can execute the NPM run build and see that the generated static resource will package all the components in the Reactui component library even if only one component is used.

So how do you load on demand?

The first thing that comes to mind is the use of the babel-plugin-import plug-in, which can be loaded on demand against component libraries in the Babel configuration.

The user needs to install the babel-plugin-impor plug-in and add the configuration to the plugins:

"plugins": [["import",
    {
      "libraryName": "reactui".// The name of the transformation component library
      "libraryDirectory": "dist/components".// Convert the path
      "camel2DashComponentName":false.// Set to false to prevent conversion of component names
      "style":true}]]Copy the code

Use the following method in the demo project:

import { Button } from 'reactui';
Copy the code

This will compile in Babel to:

import { Button } from 'reactui/dist/components/Button';
require('reactui/dist/components/Button/style');
Copy the code

But there are some downsides:

1. Users need to install babel-plugin-import and configure plugins when using the component library.

2. When developing the component library, the corresponding style file of the component should also be placed under the Style folder;

Is there an easier way? Antd JS code supports tree shaking based on ES Modules by default. That’s right! You can also use WebPack’s new technology, tree Shaking.

What is tree shaking? AST is a Syntax Tree derived from the Syntax analysis of JS code. The AST syntax tree converts every statement of a JS code into a node in the tree. DCE Dead Code Elimination The Elimination of unwanted Code without changing the result of Code execution.

Webpack 4X already uses Tree Shaking technology. We need to configure the parameter “sideEffects”: false in the package.json file to tell Webpack to remove unused modules when packaging. At this point, the user does not need to do anything when using the component library in the Demo project, and can reference JS resources as needed. I don’t know if you noticed when you looked at this that there was a problem with sideEffects being set to false. Because if you follow the above configuration, you will find that the component style is missing!!

Import ‘./button. SCSS ‘, we can see that it is only importing styles, not like other JS modules that make calls behind them. So when you configure sideEffects you need to exclude CSS files:

"sideEffects": [
  "*.scss"
]
Copy the code

With the tree Shaking approach above, component libraries can be loaded on demand. Packaged files remove unused component code and save configuration for users.

8. Styles are loaded on demand

Generally speaking, component library JS is loaded on demand, but the style file usually outputs only one file, that is, package all the files in the component library into a single index.css file, which the user can import into the project. But what if you just want to make style files that load components on demand?

Since.tsx files are packaged by the TS compiler and do not handle SCSS, I use Node-sass to compile SCSS files. If I need to load SCSS files on demand, The index. TSX file of each component needs to import the corresponding SCSS file:

import Button from './button';
import './button.scss';
export default Button;
Copy the code

Generated SCSS files also need to be packaged into each component, rather than generated into a file:

Therefore, the sass.render function in Node-sass is used to extract the style file from each file and package it into the corresponding file, as shown below:

// omit import import
function createCss(name){
    const lowerName = name.toLowerCase();
    sass.render({ // Call the node-sass function to compile the specified SCSS file to the specified path
        file: currPath(`.. /src/components/${name}/${lowerName}.scss`),
        outputStyle: 'compressed'.// Compress
        sourceMap: true,},(err,result) = >{
        if(err){
            console.log(err);
        }
        const stylePath = `.. /dist/components/${name}/ `;
        fs.writeFile(currPath(stylePath+` /${lowerName}.scss`), result.css, function(err){
            if(err){
                console.log(err); }}); }); }Copy the code

In this way, the SCSS file is added to each component in the generated dist file. The user will call the corresponding index file when importing the component through the method in the “Load on Demand section”, and the corresponding SCSS file will be called in the index.js file. Thus, style files can be loaded on demand.

There is a problem, however, with the introduction of the SCSS file import ‘./button.scss’ in the index. TSX file of each component during component library development; , so the files compiled by Node-sass need to be files with SCSS suffix (although they are already in CSS format). If CSS files are generated, users will report an error when using components because they cannot find SCSS files. In other words, when using components, You also need to install the Node-sass plug-in. I do not know if we have a better way, in the development of the component library is the use of SCSS file, compiled to generate the CSS suffix of the file, in the user’s use of the component is also called CSS file? Feel free to comment at the end of this article

conclusion

From the beginning, I decided to use the existing create-React-App scaffolding and DOCZ to form the core functions. From the website deployment of documents and the release of NPM resources, I felt that I could build the whole component library quickly. In fact, if you want to modify these existing libraries to achieve the desired effect, or through some exploration, but the whole process of exploration is also a harvest and fun, I wish to pass through the partners can gain ~

Refer to the article

[1] NutUI Component Library: nutui.jd.com/#/index

[2] EeP-React: Yep-react.jd.com

[3] React official website: reactjs.org/warnings/in…

[4] storybook: storybook.js.org/

[5] docz: www.docz.site/

[6] docz official documentation: www.docz.site/docs/gatsby…

[7] Netlify: app.netlify.com/teams/zheny…

[8] Storybook 5: How to build a Storybook library: jelly.jd.com/article/5f0…