In the process of building a component library, this paper mainly discusses the project management of the component library from the contractor, the project construction of the component library in scaffolding, and finally the constraint of the tool to the project specification. At the same time, the author summarizes some problems encountered in the development of component project library and shares common tools.

Lerna + Yarn Management project

Before we build the building, we lay the foundation and construct our project.

For a component library, we manage each component as a separate package. When maintaining excessive packages, there is always a dilemma: should these packages be maintained in a single repository, i.e. in the form of multi-project multi-repository, which we also call Multirepo, or manage multiple modules/packages in a project repository Repo, which we call Monorepo?

There are many large open source projects using this approach, such as Taro,Babel,React, Meteor, Ember, Angular, Jest, Umijs, Vue, etc. Almost all the warehouses we are familiar with adopt monorepo without exception. We can see that the contents of the first level directory of these projects are mainly scaffolding. The main contents are managed in packages directory and divided into multiple packages.

The directory structure is as follows:

├ ─ ─ packages | ├ ─ ─ two packages pkg1 | | ├ ─ ─ package. The json | ├ ─ ─ pkg2 | | ├ ─ ─ package. The json ├ ─ ─ package. The jsonCopy the code

Let’s talk about the pros and cons of both

Multirepo:

  • advantages
    • Each module has a high degree of freedom in management, and can choose its own building tools, dependency management, unit testing and other supporting facilities.
    • The volume of each module is generally not too large.
  • disadvantages
    • Scattered warehouses are hard to find, and even harder when there are many, and branch management is chaotic.
    • Version update is tedious. If the version of a public module changes, all modules need to be updated depending on it.
    • CHANGELOG can not automatically associate the changes of various modules with each other. It basically relies on word of mouth.

Monorepo:

  • advantages
    • One ruthlessly maintains multiple modules without having to hunt around for warehouses.
    • Convenient version management and dependency management, reference between modules, debugging are very convenient, with the corresponding tools, can be a command G.
    • It facilitates unified production of CHANGELOG and automatically produces CHANGELOG when publishing with the submission of specifications.
  • disadvantages
    • Unified build tools, build tools put forward higher requirements, to be able to build a variety of related modules.
    • The warehouse will get bigger.

Based on the advantages and disadvantages of the two, combined with the characteristics of the component library:

  • There are dependencies between each package.
  • Unified build tools, unified version.
  • Requirements for version description are high

Therefore, Monorepo is recommended for component library management. Currently, the most common Monorepo solution is the workspaces feature of Lerna and Yarn, which is based on the Monorepo workflow of Lerna and Yarn Workspace. However, Lerna and Yarn overlap in function upstream. Yarn is recommended to handle dependency issues and Lerna is recommended to handle release issues.

Installation of Yarn

npm install -g yarn
Copy the code

Install Lerna

yarn install lerna -D
Copy the code

Initialize the Lerna project

<! --> CD project <! Initialize --> lerna initCopy the code

After initialization, lerna.json is the management configuration file for Lerna

prokect /
  packages /
  package.json
  lerna.json
Copy the code

Then the fixed pattern of Lerna is adopted and all components under Packages/Components are managed by package

Fixed mode, versioning by lerna.json version. When you execute lerna publish, if only one module has been changed since the last release, the corresponding module version will be updated to the new version number, and then you can only publish the changed library. This model is the same one Babel uses. If you want all versions to change at once, you can update the Minor version number, which will cause all modules to be updated.

Json {"packages": [" Packages/Components /*/*"], "version": "0.0.1", "npmClient": "YARN ", "useWorkspaces": true }Copy the code

Let’s complete the main directory. This is what our project repository looks like at this point

ComponentA/componentB/componentB index.jsx Json lerna. Json Lerna configuration file rollup.config.js rollup configuration file babel.config.js Babel configuration fileCopy the code

Rollup Build project

Once the foundation is laid, how can the scaffolding be missing as a worker? Let’s start to build the scaffolding

The most commonly used component library packaging tool in the industry is Webpack, and Webpack will be more suitable for the packaging work of UI component library projects due to its own use scenarios. But for reasons of my own project, I used Rollup for packaging here.

Rollup bias is applied to JS library, Webpack bias is applied to front-end engineering, UI library; If your application scenario is just JS code and you want to do ES conversion and module parsing, use Rollup. If your scenario involves CSS, HTML, and complex code splitting and merging, webPack is recommended.

Install dependencies

Install dependencies

yarn add -W -D rollup rollup-plugin-terser rollup-plugin-progress  @rollup/plugin-alias  @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-replace @rollup/plugin-url
Copy the code

Install babel-related dependencies

yarn add -W -D @rollup/plugin-babel @babel/core @babel/preset-env @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread
Copy the code

Install styles handle dependencies

yarn add -W -D rollup-plugin-postcss postcss-plugin-pxtoviewport postcss-url autoprefixer rollup-plugin-postcss-inject-to-css
Copy the code

This section describes common plug-ins

Commonly used:

The plug-in instructions
rollup-plugin-terser Helps us achieve code compression during packaging
rollup-plugin-progress Output the progress of Rollup packaging
@rollup/plugin-alias Provides the ability to alias modules, which should be familiar to anyone who has used WebPack
@rollup/plugin-commonjs Help us support the CommonJS module
@rollup/plugin-json Help us convert.json toES6The module
@rollup/plugin-node-resolve Help us innode_modulesFind and bundle third-party dependencies in
@rollup/plugin-replace Replace a string in a file when you bundle it
@rollup/plugin-url Inline import files as data URIs or copy them to output directories

Babel related:

The plug-in instructions
@rollup/plugin-babel Help us toES6And so onES5
@babel/preset-env A smart preset lets you use the latest JavaScript without micromanaging the syntactic conversions (and optional browser polyfill) required for the target environment, with smaller JavaScript packages
@babel/plugin-proposal-decorators JSX syntax that allows the use of modifiers
@babel/plugin-proposal-class-properties Used to compile class components
@babel/plugin-proposal-object-rest-spread Used to use the spread operator

Handling style related:

The plug-in instructions
rollup-plugin-postcss For handling sass/less/stylus
postcss-plugin-pxtoviewport Let’s change px to VW units
postcss-url Processing styleurl()Image, copy to output directory or transfer to Base64 and other operations
autoprefixer Automatically add style attributes-webkit-Such prefixes are used for compatible browsers
rollup-plugin-postcss-inject-to-css The postcssinjectStyle references in patterns are converted from inline to inline

Rollup Rollup Rollup Rollup Rollup Rollup Rollup Rollup

// rollup.config.js import NodePath from 'path' import autoprefixer from 'autoprefixer' import url from 'postcss-url' import px2vw from 'postcss-plugin-pxtoviewport' import RollupJson from '@rollup/plugin-json' import RollupAlias from '@rollup/plugin-alias' import RollupUrl from '@rollup/plugin-url' import RollupBabel from '@rollup/plugin-babel' import RollPostcss from 'rollup-plugin-postcss' import RollPostcssInject2Css from 'rollup-plugin-postcss-inject-to-css' import RollProgress from 'rollup-plugin-progress' import RollupCommonjs from '@rollup/plugin-commonjs' import RollupNodeResolve From '@rollup/plugin-node-resolve' // Babel config const rollBabelConfig = {babelHelpers: } // postcss config const rollPostcssConfig = {inject: true, plugins: [url({url: 'inline', maxSize: Px2vw ({viewportWidth: 750, // The width of the window corresponds to the width of our design, usually 750 unitPrecision: ViewportUnit: 'vw', // Specify a window unit to convert to. Vw selectorBlackList is recommended: ['.ignore', '.hairlines'], // Specify a class that is not converted to Windows units. It can be customized, and can be added indefinitely. 1 // less than or equal to '1px' does not convert to window units, you can also set to values you want}), autoprefixer({remove: false})], extensions: ['.css', 'SCSS ']} export default {input: './packages/main.js', output: [{format: 'es', './lib', // output to the lib folder preserveModules: true, // retain modules, output as many chunk preserveModulesRoot: 'packages'}], external: [ 'react', 'react-dom' ], plugins: [ RollupAlias({ entries: [{ find: '@', replacement: NodePath.join(__dirname, 'packages') }] }), RollupBabel(rollBabelConfig), RollPostcss(rollPostcssConfig), RollupNodeResolve({ customResolveOptions: { moduleDirectory: 'node_modules' }, rootDir: NodePath.join(__dirname, '.'), browser: true }), RollupCommonjs({ include: /\/node_modules\// }), RollupJson(), RollupUrl(), RollProgress(), RollPostcssInject2Css({ exclude: /\/node_modules\// }) ] }Copy the code

Babel configuration

// babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env', {
        modules: false,
      }
    ]
  ],
  plugins: [
    [
      '@babel/plugin-proposal-decorators', {
        legacy: true,
      }
    ],
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-proposal-object-rest-spread',
    '@babel/plugin-transform-runtime'
  ]
}
Copy the code

Optimization of packaging

File compression

Rollup will count incoming code and eliminate those that are not used. This allows you to build on existing tools and modules without having to add additional dependencies or inflate the size of your project.

In addition to the tree shaker optimization of Rollup itself, the packaged code needs to be compressed to minimize code volume. Here we use the rollup-plugin-terser plugin.

The installation

yarn add -D -W rollup-plugin-terser
Copy the code

The use of plug-in

import { terser } from "rollup-plugin-terser" export default { ... Plugins: [... terser()]}Copy the code

However, in general project engineering, webpack uses Uglifyjs-webpack-plugin or terser-webpack-plugin to compress the code, so in fact, the component library pressure is not compressed. Without compression, developers can use node_modules to read the source code and debug it more comfortably.

According to the need to introduce

A component library tends to have more components, and as more components grow, the size of the entire component library gets bigger and bigger. This can be fatal, and component library users will only import individual modules in most cases. So it is essential that components be loaded on demand. Currently, component libraries such as ANTD-UI and Element-UI provide on-demand functionality

Components are imported on demand

The introduction of components on demand is relatively easy. Thanks to the Ouput. PreserveModules property of Rollup, modules can be compiled and preserved and exported to the Ouput directory during packaging.

Here’s an example:

/ / the source code: ComponentA/componentB/componentB index.jsx index.scSS Main.js entry fileCopy the code

main.js

Export {default as componentA} from './components/componentA' export {default as componentB} from './components/componentB'Copy the code
  • Without usingpreserveModulesThe export effect of
lib /
  main.js
Copy the code

All components are packaged into a single file

  • usepreserveModulesThe export effect of
Lib/components/componentA/componentB/componentB index.js main.jsCopy the code

In fact, both are in use

import { ComponentA, ComponentB } from 'xxx'
Copy the code

However, if preserveModules are used, we can introduce component modules separately

import ComponentA from 'xxx/components/componentA
Copy the code

The on-demand component import problem was solved, but in a real development project, it would be very troublesome to write such a long path for each component.

Here I recommend a webpack-plugin-import plugin for WebPack, which can avoid the need to manually introduce component styles in the development process and automatically introduce them in an engineering way.

Note that this refers to the process of developing projects to introduce components, not component library projects

The installation

yarn add -D -W webpack-plugin-import
Copy the code

use

// babel.config.js plugins: [ [ 'import', { libraryName: 'xxx', camel2DashComponentName: CustomName (name) {return 'XXX /components/${name}'}}]] customName (name) {return 'XXX /components/${name}'}}]Copy the code

The effect

Import {ComponentA, ComponentB} from 'XXX';  import ComponentA from 'xxx/components/componentA import ComponentA from 'xxx/components/componentACopy the code

At this point, the on-demand import of components is finally complete.

The introduction of style files on demand

Rollup-plugin-postcss plug-ins have two styles of packaged output modes

  • Extract package bundles out an entry CSS file
  • Inject dynamically inserts component styles into js modules<style>tag

The options. Inject property of rollup-plugin-postcss plugin and inline insert the styles introduced by the component.

// componentA a.css.js import 'componentA a.css.js' // componentA a.css.js // componentA a.css.js Export default injectStyle('.classa {height: 20px}. ClassA__text {font-size: 16px}')Copy the code

However, the use of inject will bring a problem, that is, the

To solve this problem, the rollup-plugin-postcss-inject-to-css plug-in can be used here to change the inline import of styles to the inline import.

After the rollup-plugin-postcss-inject-to- CSS plug-in is used:

// componentA index.js
import 'A.css'
// componentA A.css
.classA {height: 20px} .classA__text {font-size: 16px}
Copy the code

The constraint specification

With component library management and build capabilities, do you think you’re done just waiting to develop components? NoNoNo… As a worker, to build a house, dealing with contractors – management and scaffolding – construction tools, but also need a lot of workers – development students. In a multi-stakeholder project, there must be a specification to enforce the constraints, and the specification needs to be automated and mandatory in addition to the written specification.

Common constraint specifications for projects include esLint for code formatting and writing, and stylelint for styling properties. But there are always workers who like to be lazy, and there will often be some operations that esLint doesn’t work because the editor is not configured properly, etc. Some are even more egregious, each time a file is saved, it will automatically format the file according to its own editor configuration preferences O (╥﹏╥)o. Once you commit to git repository, you’re done… Everyone else is waiting for a bunch of conflicts to resolve…

Therefore, we need to verify the code of each worker before the code submission to prevent random submission.

Husky Git commits the hook

Here we are using Husky to commit GIT to add hooks to perform some validation that we need to do, and execute some scripts to verify that the code is compliant.

The installation

npm install husky -D
Copy the code

use

// package.json { "husky": { "hooks": { "pre-commit": "npm test", "pre-push": "npm test", "..." : "..." }}}Copy the code

Git commit (NPM test) git commit (NPM test) git commit (NPM test) git commit (NPM test)

lint-staged

Lint-staged is a tool for running Linters on Git staging files. You can use husky to check your code before git commit.

The installation

npm install lint-staged -D
Copy the code

use

// packages.json
{
  "lint-staged": {
    "**/*.(js|jsx)": [
      "prettier --config .prettierrc --write",
      "eslint --fix",
      "git add"
    ],
    "**/*.scss": [
      "prettier --config .prettierrc --write",
      "stylelint --syntax=scss --fix",
      "git add"
    ],
    "**/*.css": [
      "prettier --config .prettierrc --write",
      "stylelint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}
Copy the code

My setup here is to check esLint, stylelint, and format code using Prettier in the directory before git commit. Make sure that everything we commit to GIT repository is consistent.

Commitlint submits information for verification

With Husky enabled, we have the ability to do a few things with Git hooks, starting with code submission specifications and validation of specifications, elegant submission, easy teamwork, and quick problem location.


:

Common type categories

  • Upd: Update a feature (not feat, not fix)
  • Feat: New Feature
  • Fix: Fixes bugs
  • -Jenny: There are some docs.
  • Style: format (changes that do not affect code execution)
  • Refactor: refactoring (i.e. code changes that are not new features or bug fixes)
  • Test: Adds a test
  • Chore: Changes to the build process or helper

The installation

npm i -D commitlint @commitlint/cli @commitlint/config-conventional"
Copy the code

use

// packages.json
{
  ...
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
Copy the code

Of course, it might be annoying to manually enter every commit, so we can use Git Cz instead

Commitizen and CHANGELOG

Commitizen installation

npm install -g commitizen
Copy the code

Install changelog and generate the tool of Changelog

npm install -g conventional-changelog conventional-changelog-cli
Copy the code

Check whether the installation is successful:

npm ls -g -depth=0
Copy the code

Run the following command to support Angular’s Commit message format:

commitizen init cz-conventional-changelog --save --save-exact
Copy the code

Go to the project directory and run the following command to generate the Changelog. md file:

conventional-changelog -p angular -i CHANGELOG.md -s
Copy the code

Git cz: Git cz: git cz: git cz: git Cz: Git Cz: Git Cz: Git Cz: Git Cz: Git Cz

conclusion

This article mainly combs me from the zero development of a component library encountered problems step down from the pit and the actual use of tools and methods, I hope to bring help to you.

Do a team infrastructure, from looking for a contractor to manage the project, to putting up scaffolding to facilitate construction, and then to standardize the construction work of workers. Are step by step to step on the pit. In addition to the more common tools and methods mentioned above, there are many explorations of front-end engineering/automation. This includes CI/CD for more validation of submissions and so on.

References:

  1. Lerna + yarn project best practice [blog.csdn.net/i10630226/a…].
  2. Using the basis of lerna [www.jianshu.com/p/8b7e60253]…
  3. Git cz Commitzen usage] [segmentfault.com/a/119000002…
  4. A Rollup Chinese document [segmentfault.com/a/119000002…].
  5. GitHook tools – huksy configuration [blog.csdn.net/huangpb123/…].