DEV Community

Mark
Mark

Posted on

How to Build Component Libraries

Everyone has used component libraries. Popular React component libraries include Alibaba's Ant Design, ByteDance's Semi Design, Arco Design, and so on. But how are these component libraries packaged? If we want to create our own component library, how would we write the packaging logic? In this article, we will explore these questions.

Create a new project.

mkdir component-lib-test
cd component-lib-test
npm init -y
Enter fullscreen mode Exit fullscreen mode

Install Ant Design, Arco Design, and Semi Design.

pnpm install antd
pnpm install @douyinfe/semi-ui
pnpm install @arco-design/web-react
Enter fullscreen mode Exit fullscreen mode

npm and yarn will flatten all dependencies, which can look quite messy. But pnpm won't, making the node_modules directory clean.

Image description

Firstly, let's take a look at Ant Design, which is divided into three directories: lib, es, and dist.

Image description

Let's take a look at the component code in these three directories.

The components in the lib directory are in commonjs format:

Image description

The components in the es directory are in ES module format:

Image description

The components in the dist directory are in UMD format:

Image description

Then, the entry points for commonjs, esm, umd, and types are declared in the package.json:

Image description

In this way, when you use 'require' to import, you are importing components from the 'lib' directory, and when you use 'import', you are importing components from the 'es' directory. And if you directly import using a script tag, you are importing components from the 'unpkg' directory.

Let's take a look at 'semi-design':

Image description

It is the same.

Image description

The only difference is the addition of a 'css' directory. Antd does not have this directory because it has switched to a CSS-in-JS solution, eliminating the need to import CSS files separately.

Next, let's look at 'arco-design':

Image description

It is the same too.

Image description

It also has three directories: 'lib', 'es', and 'dist'. The entry points for 'esm', 'commonjs', and 'umd' are declared as well.

In other words, component libraries are all like this. They separately package three versions of the code (esm, commonjs, umd), and then declare the entry points for different module specifications in the package.json.

So, the question is, if I have an esm module, how can I build three versions of the code (esm, commonjs, umd) separately?

This question is easy to answer.

For the UMD bundle, you can just use Webpack to package it.

For ESM and CommonJS, there's no need to package them, just compile them with tsc or babel.

Let's take a look at how each of these three component libraries did.

Firstly, arco-design, Its packaging logic is under the arco-scripts of arco-cli:

Image description

Let's take a look at index.ts

Image description

There are 3 methods for building three types of code and 1 method building CSS.

Let's take a look at each one:

Image description

For the compilation of ESM and CJS, it encapsulates a compileTS method, and then passes in different types.

Inside compileTS, you can compile with tsc or babel:

Image description

Compiling with tsc involves reading the tsconfig.json in the project, and then compiling:

Image description

Babel compilation is based on built-in configurations, it modifies the product modules specification, and then compiles:

Image description

The babelConfig is configured for TypeScript and JSX compilation:

Image description

Next is UMD, As we said, it indeed uses webpack for packaging:

Image description

In the webpack configuration, you can see that it is indeed prepared for unpkg, using ts-loader and babel-loader:

Image description

And for the CSS part, it uses Less for compilation:

Image description

Gulp is used to organize compilation tasks, allowing tasks to be executed in sequence or in parallel.

Here, gulp.series is used to execute tasks in sequence, while gulp.parallel is used for parallel execution.

So, it's clear how those three types of bundle along with the CSS files are packaged:

Image description

Gulp is used here just to organize compilation tasks, it can be used or not used.

Let's take a look at semi-design now:

Image description

It doesn't have a separate xx-scripts package, it's directly under the scripts directory of semi-ui.

It also uses gulp to organize tasks.

Image description

Let's take a look at this compileLib gulp task:

Image description

Here, compileTSXForESM and ForCJS are clearly used for compiling components into two types of code, ESM and CJS, respectively.

Firstly, it uses tsc for compilation, then it uses babel:

Image description

Then for UMD, it also uses webpack:

Image description

It uses babel-loader and ts-loader:

Image description

Finally, the compilation of SCSS:

Semi-design maintains all component SCSS files under the semi-foundation directory:

Image description

So, the compilation process looks like this:

Image description

It compiles all SCSS files under the semi-foundation directory and merges them into one file.

Image description

The styles of arco-design are maintained under the component directory.

Image description

There's not much difference here, you just need to change the source directory when compiling.

This is how the esm, cjs, umd, and scss of semi-design are compiled and packaged.

Image description

Is there a big difference from the scripts of arco-design?

Not really, it's just that there is no separate package for xxx-scripts. The esm and cjs code are compiled using tsc + babel, and scss is used instead of less.

Let's take a look at ant-design:

It also separately maintains a package for compilation and packaging scripts, called @ant-design/tools.

It also has a gulpfile that defines many tasks.

For example, the compile task is compiling source code to es and cjs:

Image description

It also firstly uses tsc, then babel for compilation, and finally outputs to the es or lib directory:

Image description

Webpack is also used when packaging umd code:

Image description

The only difference is that its Webpack configuration file is read from the component library project directory, instead of being built-in like in arco-design.

This is the compilation and packaging logic of these three component libraries.

Is there a big difference?

Not really, you could even say they are almost the same.

Summary

We have analyzed the output and compilation packaging logic of the ant-design, semi-design, and arco-design component libraries.

They all have lib, es, dist directories, which contain component code in commonjs, es module, and umd formats respectively.

And they declare the three types of specifications in package.json using main, module, and unpkg.

From a product perspective, the three component libraries are quite similar.

Then we analyzed the compilation and packaging logic.

Ant-design and acro-design both have a separate package for scripts, while semi-design does not.

For compiling esm and cjs code, they all use babel and tsc for compilation, but arco-design uses either tsc or babel, while ant-design and semi-design first use tsc for compilation and then babel.

For packaging umd code, all three component libraries use webpack, but some have built-in webpack configurations, while others place them in the component library project directory.

In terms of styles, ant-design uses a css-in-js runtime solution that does not require compilation, while arco-design uses less and maintains the styles in the component directory, and semi-design uses scss and has a separate directory to store all component styles.

And the compilation tasks are all organized using gulp, which can execute tasks in series or in parallel.

Although there are some minor differences, overall, the compilation and packaging logic of these three major component libraries can be said to be almost the same.

Is it troublesome to write such scripts?

Not at all. Everyone knows how to package umd parts with webpack, and it's not difficult to compile esm and cjs with babel or tsc. As for scss and less, that's even simpler.

So, compilation and packaging is not the difficult part of a component library.

If you want to write a component library, you can also write scripts in this way.

Top comments (1)