How to Build Component Libraries
Tags: webpack, gulp, vite
Published: 2024-06-04 09:00:37

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.

```bash
mkdir component-lib-test
cd component-lib-test
npm init -y
```

Install Ant Design, Arco Design, and Semi Design.

```
pnpm install antd
pnpm install @douyinfe/semi-ui
pnpm install @arco-design/web-react
```

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qldo1scdd9etxw8fakmz.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7x9nu35dky08lo56ari.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxv1pym7u0genrr6a8zj.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7n7dpuzp5s8x2966zi8.png)

The components in the dist directory are in UMD format:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8tankc1z8c9u8dq9f25.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjz3aa5jgbnca0t4msju.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3a9euyf7z070mer9ppaj.png)

It is the same.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0befzlyduo9njl1x13m5.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zddpuaztxq5pkrx40me.png)

It is the same too.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4nlr6rr306xx21qzr4q.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6eoq67r1sxkdm3hdgsdp.png)

Let's take a look at index.ts

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vwn394jkus6wup8pra1.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhmobaut2h58nj8cdqoe.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2g67rtwa22kfmfcsx6oa.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xsfud6ttq9lik2kfzh2.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edz5wpo2pnduz145yd62.png)

The babelConfig is configured for TypeScript and JSX compilation:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0v9a81ndf3k02rg4cezi.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1t4492s6vtwicthiyhz.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c5qtp0heg9se41oi51ey.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo36umi0s2aboiymw5m9.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1czlh0nj3ulbhyz361e8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnvj87i55p4uzehl6z74.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/am1yiwnrnsgjdtsd05cl.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqag9degv5wq9m8cl8f4.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzz2t8clsztl55qvmmyu.png)

Then for UMD, it also uses webpack:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi2frhwxgj1fsbdsmfi3.png)

It uses babel-loader and ts-loader:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/habdb2hgd09w7k5uhd64.png)

Finally, the compilation of SCSS:

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3xhjem1nawen8sxo3fs.png)

So, the compilation process looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rohhua5lz1lodjpse0nc.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnhd8c5z4l6xc0nbcmul.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkat1kqqpjmog0aab8rr.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ymh6ndq0yp95dr45dg0v.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1981ndbxnwkhko9c6t2l.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vn6ilxyuwuhm6nudf5w.png)

Webpack is also used when packaging umd code:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8h62697ydg246yyw5jd7.png)

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.