preface

With the increasing number of internal development projects, component maintenance becomes increasingly difficult.

One of the dilemmas that front-end students often face. After the development of project A, A large number of common components are encapsulated under the project. When project B was launched, many of the components of project A could be copied directly to project B due to the similar design style.

Projects A and B were developed, followed by projects C and D. Manual replication, repeated every time a new project is launched, is tedious and inefficient.

It’s okay to just copy the component code of an old project when a new one starts.

If one day the product manager asks for A style change for A common component under project D, and the corresponding common components under project A, B, and C are updated simultaneously, the mechanical labor becomes endless as the number of projects increases.

From the perspective of the projects already produced by the enterprise, this paper extracts the react component developed before and encapsulates it into a business component library instead of creating a general component library. What are the differences between the extracted business component library and the general component library?

  • The component library we build is mainly business components, and the object of service is inside the enterprise. Therefore, the component library we develop may contain many business images, such as the company’slogo.
  • In general, all components of a generic component library are from0Business components, however, rely on third-party component libraries for much of their functionality (e.gantd,Element UI) do secondary development. Therefore, a business-oriented component library is likely to integrate third-party component library dependencies.

The source code is at the end of the article and can be downloaded to start running.

implementation

Project structures,

First, create an empty project locally and create a folder SRC/Components. All components that need to be wrapped can be copied under components (as shown below).

The example above encapsulates four components and creates a separate entry file called index.ts.

Index. ts internally imports and exposes all components under Components to external calls.

Export {default as Button} from "./Button"; export { default as Icon } from "./Icon"; export { default as Logo } from "./Logo"; export { default as Empty } from "./Empty";Copy the code

Take a look at the file structure of the Components /Button component (as shown below).

The Button component contains two files, a less style file and the logical code index.tsx.

The index. TSX content of the Button component is simple, introducing the equivalent less style, writing the component logic, and exporting it for external use.

import React from 'react'; import './index.less'; type defaultProps = { text: string; // button copy onClick? : any; // Click event}; const Button = (props: defaultProps) => { return ( <div onClick={props.onClick}> {props.text} </div> ); }; export default Button;Copy the code

After the file structure is introduced, the configuration file of Webpack is written to package the component library code for output (the code is shown below).

The idea of the webpack configuration file is very simple. The entry is set to the index.ts exposed in the SRC/Components directory of the component library source code, and the output is set to the dist folder.

Rules mainly deals with TSX and less files. TSX files are the logical code of all components to be encapsulated, because the component code is written using TS syntax, so use TS-Loader to convert TS syntax into JS, and then use Babel to compile (specific configuration details can be viewed in the source code).

Less files need to be compiled into CSS using less-Loader, and then use PostCSS. Plug-ins are used to add prefixes to CSS that meet the compatibility of major browsers.

After postCSS is processed, it is passed to CSS-loader for processing. Finally, MiniCssExtractPlugin is used to extract all style files and put them into app.css.

// webpack.config.js file const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); Module.exports = {entry:path.resolve(__dirname," SRC /components/index.ts"), // entry: "development", output: Library: 'uI-design-demo ', libraryTarget: 'umd', filename: 'bundle.js', path:path.resolve(__dirname,"dist") }, resolve: { extensions: ['.tsx', '.ts', '.js'], }, module:{ rules: [ { test: /\.tsx?$/, use: ['babel-loader','ts-loader'], exclude: /node_modules/, }, { test: /\.(le|c)ss$/i, use: [ { loader:MiniCssExtractPlugin.loader }, { loader: "css-loader" }, { loader: 'postcss-loader', options: { postcssOptions: { plugins: [' autoprefixer],}}}, / / will be less compiled into CSS 'less - loader'],}}, plugins:[ new MiniCssExtractPlugin({ filename:"app.css" }) ] };Copy the code

The resulting files from webPack are shown below. The JS code for all components is wrapped in bundle.js, and the style files are separately pulled out and put into app.css.

If you publish the DIST directory to the NPM repository, how can other projects introduce this component library?

Debugging is introduced into

To enable other projects to import the currently wrapped component library, first modify two fields in package.json in the component library root (as shown below)

Name is the name of the custom component library. When other projects import components from the component library through import or require,name corresponds to the imported component library name.

Main sets the entry file address for the current component library.

Component library code is not set in stone once it is developed, and it may change functionality or style from time to time based on reality.

If you modify the component library code, publish it to the NPM repository for other projects to install and use, the process is too cumbersome.

NPM Link simplifies this process by directly debugging component library code locally.

To create a new React test project on your local desktop, complete the following three steps to introduce the component library directly into the test project.

  • Execute at the root of the component librarynpm linkCommand to link the current component library globally
  • Run the command at the root of the component library to link to the test projectreactFailure to perform this step may result in an error, for examplenpm link D:\react-ui-test\node_modules\react
  • Under testreact-ui-testRun commands in the root directorynpm link ui-design-demoLink component libraries,ui-design-demoThat’s the definition abovename

After the above three steps, the test project can be imported directly into the components in the component library for testing (code below).

// app. js file import React from "React "; import { Button } from "ui-design-demo"; import "ui-design-demo/dist/app.css"; function App() { const clickEvent = () => { console.log(123); Return (<div className="App"> Hello world <Button call=" Button "onClick={clickEvent}/> </div>); } export default App;Copy the code

Since the CSS code of the component library is separated into app.css, the test project that wants to use the component library usually introduces app.css in the project entry file index.js (the above code introduces CSS in the app component for easy viewing).

After the test project started, the operation effect picture is as follows.

With NPM Link enabled, component library debugging is easy. The test project can be a new project created temporarily or a real project that already exists.

They can be directly launched in the local component library, if the components of the component library need to be adjusted, directly modify the compilation in the component library, the test project side can be refreshed in real time to observe the effect.

Icon processing

In general, a component cannot contain only JS and CSS. ICONS are used in many of our daily development components. ICONS are mainly used in the following two aspects.

  • Components within the component library need to use ICONS
  • When an external project references a component library, it needs to use the ICONS exposed by the component library

To satisfy both requirements, create a new component Icon under the component library source SRC/Components (as shown below).

The less file code under Icon is as follows, directly importing the font Icon styles downloaded from iconfont.

// index.less文件
@import url('../../fonts/iconfont.css');
Copy the code

The index.tsx file under Icon is coded as follows, encapsulating an Icon component for external use.

import React from 'react'; import './index.less'; interface defaultProps { name:string; size? :number | string; } const Icon = (props: defaultProps)=>{ return <i className={`iconfont ${props.name}`} style={{fontSize:`${props.size?props.size:12}px`}}></i>;  } export default Icon;Copy the code

Then the source code SRC /components entry file index.ts will export the Icon, and finally modify the webpack configuration file.

The configuration file webpack.config.js will add the processing of font files on the original basis. After the configuration, a fonts folder will be added in the generated dist directory, which is specially used to store font files.

// webpack.config.js file... / / to omit the module: {rules: [... {test: / \. (the vera.ttf | woff | woff2) $/, loader: "file - loader", the options: {name: '[name].[ext]', outputPath: 'fonts' } } ] } ...Copy the code

After the above work is completed, the font icon can be directly introduced into the test project for use. The code is as follows.

import React from "react"; import {Button,Icon} from "ui-design-demo"; import "ui-design-demo/dist/app.css"; function App() { const clickEvent = () => { console.log(123); } return (<div className="App"> Hello world <Button call=" Button "onClick={clickEvent}/> <Icon name="icon-zengjia" size="50"/> </div> ); } export default App;Copy the code

The renderings are as follows:

The image processing

The component library now needs to add a business component to display the company Logo, and the component library needs to integrate the image.

Create a new component Logo under SRC/Components in the component library source code. The file structure is shown below.

The index. TSX and style code inside the Logo are shown below. Div with the class name Logo needs to display an image, and index.less uses a background image to introduce the image.

Why not import images directly from the index.tsx file? Because the image path changes after the component library is compiled and packaged using Webpack, the test project may not find the image because of the wrong image path when introducing the component.

However, less can avoid the problem of image path error by loading the background image through background.

This is because the CSS is compiled once by WebPack in the component library, but the test project entry file introduces the global CSS in the component library, and the test project webpack also compiles the CSS once, thus ensuring that the image path does not fail.

// index.tsx file import React from 'React '; import "./index.less"; const Logo = () => { return ( <div className="Logo"> </div> ); }; export default Logo; -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / index. Less file. The Logo {width: 133 px; height: 114px; background: url(.. /.. /images/logo.png) no-repeat; }Copy the code

In addition, the webpack configuration file needs to add image processing (code below).

// webpack.config.js file... Module: {rules: [... / / omit {test: / \. (GIF | JPG | PNG) $/, loader: file - "loader", the options: {name: '[name].[ext]', outputPath: 'images' } } ... ] }Copy the code

After the above configuration is complete, the dist directory generated by the final package has an images folder, which is dedicated to storing the business images under the component library (figure below).

Load the third-party component library

If a component library relies on a third-party component library, such as Ant Design, you first need to install the dependent library in the root directory of the component library using NPM install ANTd –save.

Once installed, ANTD can be used directly under the component library. For example, create a new component Empty under the source code SRC/Components (see the code below).

// Empty/index.tsx import React from 'React '; import { Empty as Ant_Empty } from 'antd'; const Empty = () => { return ( <Ant_Empty image={Ant_Empty.PRESENTED_IMAGE_SIMPLE} /> ); }; export default Empty;Copy the code

The Empty component is a secondary wrapper based on ANTD that we export in the SRC/Components entry file index.ts. In addition, the import file also needs to import the ANTD style file, and export all antD internal components at the end of the file.

import 'antd/dist/antd.css';

export { default as Button } from "./Button";

export { default as Icon } from "./Icon";

export { default as Logo } from "./Logo";

export { default as Empty } from "./Empty";

export * from "antd";

Copy the code

After the above changes, the component library configuration file webpack.config.js also needs to add an item to exclude react and ANTd, so that we can pack and generate bundle.js to be lightweight.

module.exports = {
    ...
    externals: {
      antd: "antd",
      react: "react"
    }
    ...
Copy the code

After the above configuration, the test project does not need to install ANTD after importing the component library. The test project can use the custom components of the component library or directly use the components under ANTD (code below).

// app.js (test project) import React from "React "; import {Button,Icon,Logo,Switch} from "ui-design-demo"; import "ui-design-demo/dist/app.css"; function App() { const clickEvent = () => { console.log(123); } return (<div className="App"> Hello world <Button call=" Button "onClick={clickEvent}/> <Icon name="icon-zengjia" size="50"/> <Logo/> <Switch/> </div> ); }Copy the code

The test project can directly reference the component Switch defined under ANTD from UI-design-demo, but it should be noted that the user-defined component under UI-Design-Demo should not have the same name as the component under ANTD (the effect picture is as follows).

According to the need to load

This section looks at the implementation of load on Demand.

What about introducing a component library on demand in a test project? (Code below)

Loading on demand means only loading the js and CSS corresponding to the component used. The following code manually imports the JS and CSS under the Button component.

As a result, only the Button component’s code in the component library is referenced by the test project, which becomes much smaller when packaged.

Some students will say, why does antD load on demand not have to write so much trouble, it is written in the same way as full load, there is no need to manually import CSS?

This is because the babel-plugin-import plugin does its job. Even if you write in full load mode,babel-plugin-import will automatically compile the syntax into something similar to the following.

import React from "react"; import Button from "ui-design-demo/es/Button"; import "ui-design-demo/es/Button/style.css"; function App() { const clickEvent = () => { console.log(123); Return (<div className="App"> Hello world <Button call=" Button "onClick={clickEvent}/> </div>); } export default App;Copy the code

Now that you understand the mechanism of loading on demand, the component library should be packaged to create a separate folder for each component and put their JS and CSS together (as shown in the figure below).

Webpack wants to generate multiple directories according to the number of components, so the entry needs to be dynamically generated. Output also needs to create a separate folder according to the name of the component. MiniCssExtractPlugin extracts the CSS code of each component and puts it into the corresponding folder.

// webpack.lazy.config.js file const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const glob = require("glob"); module.exports = (async function () { const entries = await getEntries(); return { entry: entries, mode: "development", externals: { antd: "antd", react: "react" }, output: { library: '[name]', libraryTarget: 'umd', filename: '[name]/index.js', path: path.resolve(__dirname, "es") } module:{ ... // omit}, plugins: [new MiniCssExtractPlugin({filename: "[name]/style.css"})]}; */ function getEntries () {return new Promise((resolve) => {const module = {}; glob("./src/components/**/*.tsx", (err, files) => { files.forEach((file) => { const array = file.split("/"); const name = array[array.length - 2]; module[name] = file; }) console.log(module); resolve(module); }) }) } getEntries();Copy the code

Component document

The documentation of the component library can be automatically generated by docZ. The githup address of docZ is as follows:

 https://github.com/doczjs/docz
Copy the code

According to the description of docZ official documents, do relevant configuration step by step according to the requirements, and finally create a component description file under each component directory (the code is as follows).

/ / SRC/components/Button/Button. The MDX file - name: Button route: / Button in the menu: Business component -- import {Playground} from 'docz' import Button from './index.tsx' import './index.less' # Button Button Button <Button call=" Button "/> </Playground>Copy the code

Enter the command to launch DOCZ to view the online documentation of the component library, as shown below.

The source code

The source code