preface

This article aims to help readers quickly build their own front-end UI component library, build-package-publish, help you solve the problem of component reuse in large Web front-end applications.

React

React has continued to grow since 2014 and today is the most popular front-end framework. If you’re not familiar with React, check it out here.

Storybook

Storybook is a UI component development environment that allows you to browse the component library, see the different states of each component, and develop and test components interactively. Storybook allows you to develop your UI components independently of your app. You can quickly start developing components without worrying about application-level component dependencies and then apply them to your own app. Especially in large, cross-team applications, good component abstraction and storybook encapsulation management can greatly improve component reusability, testability, and speed of development. You can click here to see how storybook works.

Lerna

Lerna helps you manage your package collection. When your own library becomes larger, your version control, trace management, and testing become more complex. Lerna helps you solve this problem by using NPM and Git to optimize your multi-package management process.

This article assumes that you are already familiar with publishing your own NPM packages. If not, check out related articles such as how to Develop an NPM package.

Let’s build our UI component library step by step.

build

Initialize the React app.

Create a React app. Create a React app. Create a React app. Create a React app. Pay attention to your Node version (recommended >=6, you can use NVM to help you manage node version, NPX comes with NPM 5.2+ and higher).

npx create-react-app my-app
Copy the code

After the initial success you will get a project directory like this:

My - app ├ ─ ─ the README. Md ├ ─ ─ node_modules ├ ─ ─ package. The json ├ ─ ─ the gitignore ├ ─ ─ public │ └ ─ ─ the favicon. Ico │ └ ─ ─ index. The HTML │ └ ─ ─ the manifest. Json └ ─ ─ the SRC └ ─ ─ App. CSS └ ─ ─ App. Js └ ─ ─ App. Test, js └ ─ ─ index. The CSS └ ─ ─ index. The js └ ─ ─ logo. The SVG └ ─ ─ registerServiceWorker.jsCopy the code

Then execute:

cd my-app
yarn start
Copy the code

At this point, you can view your app by visiting http://localhost:3000/;

Initialize the storybook

If you are installing storybook for the first time, try the following command:

npm i -g @storybook/cli
cd my-app #(the app above)
getstorybook
Copy the code

You should get a project directory like this:

My-app ├─.├ ─ └.js#(Package dependencies in storybook)│ └ ─ ─ config. JsConfiguration file that tells storybook which set of defined components to load.├ ─ ─ the README. Md ├ ─ ─ node_modules ├ ─ ─ package. The json ├ ─ ─ the gitignore ├ ─ ─ public │ └ ─ ─ the favicon. Ico │ └ ─ ─ index. The HTML │ └ ─ ─ Manifest. json └─ SRC ├─ stories │ ├─ index.js#(Storybook component collection, where you need to add UI components that you have created)└ ─ ─ App. CSS └ ─ ─ App. Js └ ─ ─ App. Test, js └ ─ ─ index. The CSS └ ─ ─ index. The js └ ─ ─ logo. The SVG └ ─ ─ registerServiceWorker. JsCopy the code

Once you have installed it, you can then execute Yarn Run StoryBook to start the local Storybook development environment Server, visit the appropriate URL, such as http://localhost:9009/ and you will see a storybook interface with a simple example:

Develop your own components

Now let’s develop our own two Button components and add them to our storybook:

Create a new statefulreactbutton.js file in the SRC directory

import React, { Component } from 'react';

class StateFulReactButton extends Component {
  render() {
    const { handleOnclick } = this.props;

    return( <button onClick={handleOnclick}>react stateful button</button> ); }}export default StateFulReactButton;

Copy the code

Also create a new statelessreactButton.js

import React from 'react';

const StatelessReactButton = ({ handleOnclick }) => {
  return <button onClick={handleOnclick}>react stateless button</button>
};

export default StatelessReactButton;

Copy the code

To introduce components into a storybook:

Add the following code to the SRC /story/index.js file:

import StateFulReactButton from '. /.. /StatefullReactButton';
import StatelessReactButton from '. /.. /StatelessReactButton';
Copy the code
.add('StateFulReactButton', () => <StateFulReactButton handleOnclick={action('clicked')} />)
.add('StatelessReactButton', () => <StatelessReactButton handleOnclick={action('clicked')} / >);Copy the code

Access the local storybook Server and see the following screen:

Now our react components are ready.

Of course, storybook can do a lot of things with other plugins, like knobs viewing examples, you can interact directly with your custom components in your StoryBook Server interface, and intuitively verify the behavior of your components, all of which is completely removed from your app.

Apply your components

Once the development validation process for the above components is complete, you can add your group price to your app production code. For example, in this case, add the following code to your SRC/app.js:

import StateFulReactButton from '. /.. /StatefullReactButton';
import StatelessReactButton from '. /.. /StatelessReactButton';
Copy the code
<StateFulReactButton handleOnclick={() => alert("I am StateFulReactButton")} />
<StatelessReactButton handleOnclick={() => alert("I am StatelessReactButton")} / >Copy the code

Open your local app Server (http://localhost:3000) and see our button working perfectly:

5. Lerna initialization, package management

After a certain stage of front-end engineering development, you will find that there is a lot of duplication, which is a problem that all developers need to face. Component reuse provides a good solution, eliminating internal duplication as well as solving the problem of cross-team duplication. Continuing with the example of StateFulReactButton and StatelessReactButton, let’s split them into two separate packages managed using Lerna.

Install lerna:

npm install --global lerna
Copy the code

Initialize lerNA:

cd my-app #(the app above)
lerna init
Copy the code

Lerna will help you initialize Git for version management, and your project directory should look like this:

My - app ├ ─ ─ the storybook │ └ ─ ─ addons. Js │ └ ─ ─ config. Js ├ ─ ─ the README. Md ├ ─ ─ node_modules ├ ─ ─ package. The json ├ ─ ─ the gitignore ├ ─ ─ Public │ ├─ uninhibited.txt │ ├─ uninhibited.txt │ ├─ uninhibited.txt └ ─ ─ App. Test. Js └ ─ ─ index. The CSS └ ─ ─ index. The js └ ─ ─ logo. The SVG └ ─ ─ registerServiceWorker. Js └ ─ ─ StateFulReactButton. Js └ ─ ─ StatelessReactButton. Js └ ─ ─ packagesLerna package management directory, where to define and test your components└ ─ ─ lerna. Json#(Lerna profile)
Copy the code

StateFulReactButton/ SRC; StatelessReactButton/ SRC; We migrated statefulreactButton.js and statelessreactbutton.js, respectively, and created our own index.js files in the two SRC directories, like this:

#StatefullReactButton/src/index.js
import StateFullReactButton from './StatefullReactButton';
export default StateFullReactButton;
Copy the code
#StatelessReactButton/src/index.js
import StatelessReactButton from './StatelessReactButton';
export default StatelessReactButton;
Copy the code

Multi-package one layer is convenient for automatic configuration of packaging behind;

Initialize NPM packages in their respective root directories:

cd packages/StateFulReactButton
npm init
Copy the code
cd packages/StatelessReactButton
npm init
Copy the code

During the initialization, NPM will ask and give you some initial configurations. Here we pay attention to entry Point. Our two components are written based on react and ES6 syntax, and we need the packaging tool to help us package them into general JS to use.

Note: This time we want to reorganize the storybook, new StateFulReactButton/SRC/stories and StatelessReactButton/SRC/stories, Create a new index.js file for each of you (again you’ll need to modify the storybook in your root directory SRC/Stories) :

#StateFulReactButton/src/stories/index.js
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import StatefullReactButton from '.. /StatefullReactButton';

storiesOf('Stateful Button', module)
  .add('stateful react Button', () => <StatefullReactButton handleOnclick={action('clicked')} / >);Copy the code
#StatelessReactButton/src/stories/index.js
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import StatelessReactButton from '.. /StatelessReactButton';

storiesOf('Stateless Button', module)
  .add('stateless react Button', () => <StatelessReactButton handleOnclick={action('clicked')} / >);Copy the code

Modify the storybook/config. Jsstorybook configuration file:

import { configure } from '@storybook/react';

const req = require.context('.. /packages/'.true, /stories\/.+.js$/);
const loadStories = () => {
  require('.. /src/stories');  # (Load the storybook in the root directory)
  req.keys().forEach(module => req(module)); # (Load storybooks in each component directory)
};

configure(loadStories, module);
Copy the code

Open your Storybook Server in a browser and see if it works.

packaging

When it comes to packaging tools, webpack and rollup have to mention, in the front of the building complex applications, they help us break code, static resource, management is indispensable for the front-end engineering tools, both similar and different, under what scenarios on how to use you can refer to this article, in a word, for application development, Use the webpack; For library development, use Rollup.

The two button components that we’ve isolated are more like libraries, so we’re going to choose rollup, and we’re not going to go into the details of how rollup is packaged so you can do your own search. Here are a few configuration files that show how to incorporate rollup packaging into our project;

Rollup: yarn add rollup;

There are also some plugins that need to be packaged (some may not be used in your project) :

yarn add rollup-plugin-babel
yarn add rollup-plugin-node-resolve
yarn add rollup-plugin-filesize
yarn add rollup-plugin-sass
yarn add rollup-plugin-react-svg
Copy the code

Create a new file rollup.config.js in the root directory and add the following code:

import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import filesize from 'rollup-plugin-filesize';
import sass from 'rollup-plugin-sass';
import svg from 'rollup-plugin-react-svg';
import { writeFileSync } from 'fs';
import path from 'path';

const external = ['react'.'prop-types'];
const outputTypes = [
  { file: './dist/es/index.js', format: 'es' }, #(ES Modules)
];

const tasks = outputTypes.map(output => ({
  input: './src/index.js'.#(Component main entry, relative path)
  external,
  output,
  name: 'my-library',
  plugins: [
    resolve(),
    filesize(),
    sass({
      output: styles => writeFileSync(path.resolve('./dist'.'index.css'), styles),
      options: {
        importer(url) {
          return url.startsWith('~') && ({
            file: `${process.cwd()}/node_modules/${url.slice(1)}`
          })
        }
      }
    }),
    babel({
      exclude: 'node_modules/**',
      plugins: ['external-helpers'].You will need to install Babel to parse ES6.
    }),
    svg()
  ],
}));

export default tasks;
Copy the code

Then install the Babel plugin to parse ES6(some may not be needed in your project) :

yarn add babel-core
yarn add babel-cli
yarn add babel-loader
yarn add babel-plugin-external-helpers
yarn add babel-plugin-transform-object-rest-spread
yarn add babel-preset-env
yarn add babel-preset-react
Copy the code

Create a. Babelrc Babel configuration file under the root directory and write:

{
  "presets": [["env",
      { "modules": false}]."react"]."env": {
    "test": {
      "presets": [["env"]."react"]}},"plugins": [
    "transform-object-rest-spread"]}Copy the code

Let’s go back and modify the package.json configuration files for the two packages mentioned earlier:

  • StatefulReactButton/package.json
{
  "name": "statefull-react-button"."version": "1.0.0".# (Component version)
  "description": "this is my StatefullReactButton"."main": "dist/es/index.js".# (packaged component main function entry)
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    "build": "rollup -c .. /.. /rollup.config.js" # (Component package, same rollup.config.js here, relative path here)
  },
  "dependencies": {
    "classnames": "^ 2.2.5." " Add a separate dependency library for each component for comparison.
  },
  "publishConfig": {
    "access": "public" # (Component library publishing address, default is your NPM account repository)}}Copy the code
  • StatelessReactButton/package.json
{
  "name": "stateless-react-button"."version": "1.0.0".# (Component version)
  "description": "this is my StatelessReactButton"."main": "dist/es/index.js".# (packaged component main function entry)
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"."build": "rollup -c .. /.. /rollup.config.js" # (Component package, same rollup.config.js here, relative path here)
  },
  "dependencies": {
    "lodash": "^ 4.4.0"  Add a separate dependency library for each component for comparison.
  },
  "publishConfig": {
    "access": "public" # (Component library publishing address, default is your NPM account repository)}}Copy the code

At this point, our engineering is basically completed, execute the following command:

lerna bootstrap # install package dependencies for individual components
lerna run build #(package individual components using lerna and rollup)
Copy the code

You’ll see the dist folder in your two component roots, which contains the index.js file packaged for publishing.

Your project catalog should look like this:

My - app ├ ─ ─ the storybook │ └ ─ ─ addons. Js │ └ ─ ─ config. Js ├ ─ ─ the README. Md ├ ─ ─ node_modules ├ ─ ─ package. The json ├ ─ ─ the gitignore ├ ─ ─ Public │ ├─ uninhibited.txt │ ├─ uninhibited.txt │ ├─ uninhibited.txt └ ─ ─ App. Test. Js └ ─ ─ index. The CSS └ ─ ─ index. The js └ ─ ─ logo. The SVG └ ─ ─ registerServiceWorker. Js └ ─ ─ StatelessReactButton. Js └ ─ ─ packagesLerna package management directory, where to define and test your components├─ statefulactButton ├─ all the exercises, all the exercises, all the exercises, all the exercises, all the exercises, all the exercises ├─ ├─ all, all, all, all, all, all, all, all, all, all, all ├─ index.js ├─ class.js ├─ class.js ├─ class.js ├─ class.js ├─ class.txt#(Lerna profile)└ ─ ─. Babelrc └ ─ ─ a rollup. Config. Js └ ─ ─ yarn. The lockCopy the code

release

A command will bring your package online:

lerna publish
Copy the code

Open your NPM repository and see the component you just released. Then you can use your own component just like installing any other front-end library

yarn add statefull-react-button
yarn add stateless-react-button
Copy the code

In addition, storybook and Lerna both support rich CLI commands with powerful functions, please refer to their official documents for details; the processing of static resources such as testing, CSS and images is not mentioned in this article, please add them by yourself.