Nowadays, many companies are building themes or design systems to keep their branding consistent through various products. In contrast, developers need to manually copy and paste the same code—applying it across different projects. This activity is time consuming, costly, and at times inefficient resulting in design mismatches or bugs.
These problems are both solvable and avoidable when taking the following actions:
- Use 3rd party libraries like Bootstrap or Material UI. These libraries have a lot of different components, which are fully designed and ready to use with just a simple import. With many companies using the same libraries to build out design, the output is far less unique. Customizations also have some level limitations. Many style changes lead to complex overriding structures and generate bugs that take weeks or months to fix.
- Build your component as a separate repository and publish it into an npm registry. The component is individual, easily maintainable, and customizable. A word of caution—if you have multiple components in multiple repositories, management becomes more complex and time-consuming.
- Create your own components library. The approach itself is similar to building a separate repository, but all components are held as individual repositories under one main repository (mono-repo principle). With a components library, developers can easily customize and/or maintain multiple components at once—as well as publish components independently.
In this article, I demonstrate how to build a components library, what tools are needed, how to structure your project, and how to publish components to npm registry.
This demo features the following versions and tools:
- LernaJS - a tool that optimizes multi-package repositories workflow. Lerna is widely used, offers informal documentation, and is easy setup/use. (Version 3.13.0)
Creating the base setup
Before setting up any components, establish a version control system and a registry for published packages. This tutorial features GitHub and npm registry.
To set up the base:
1. Set up a new GitHub repository and initialize it with package.json and .gitignore files. A great practice is to include a README.md file with important library-related information and an .nvmrc file to declare NodeJS version.
2. Create a directory to hold all of the components.
5. Configure webpack. Run npm i webpack webpack-cli —save-dev —save-exact to install it.
6. Create a new file named webpack.config.js at the root of the project and setup a Babel loader.
After these steps, the structure should look like the image below.
Setting up Storybook
There are two ways Storybook handles stories—either by writing component stories in one file or creating multiple files with separated stories. I prefer the latter option because it’s cleaner and easier to manage in the longer period of time. Install Storybook by running npm i @storybook/cli @storybook/react —save-exact command in your terminal.
To set up Storybook:
1. Add a new directory named .storybook.
2. Inside the directory, create a new file - config.js and add the following lines of code inside of it: This piece of code looks up every file that has *.stories.js ending inside of the directory where components exist.
3. To add components to the Storybook setup (e.g., including SCSS support to your components), apply a specific loader for both configs—one config which bundles components and another Storybook webpack config.
4. When using webpack, share the code below. Move webpack rules or any other settings into separate files and include them into both configs.
5. Launch Storybook by adding the below line in package.json scripts and then run the script.
6. When complete, structure should look like the below.
Adding a components structure
Components libraries feature multiple repositories inside a repository—which means a component must have a package.json file. The library should also include a README.md file for a specific component related information, a LICENCE file for copyright, a CHANGELOG.md file to track component changes when a new component version is released, and if it is needed a .npmignore file which excludes files or directories from your component package. All scripts inside a package.json file at the component level are applied only to the specific component.
After taking the above steps, the component structure should look like the below image.
Lerna tracks all of the package.json files inside the project and applies any process for each of the project files.
To install Lerna, run npm i lerna —save-dev —save-exact.
Then, create a path for Lerna to find all of the components and set version to independent so that Lerna treats each component version independently. Simply add the following lines of code in your lerna.json file shown below.
Running scripts for components
With Lerna present, now it is possible to run scripts for each component. Build scripts are critical.
To build scripts:
1. In a webpack.config.js file inside the root of the project, provide an entry file of the component, and declare bundle output destination.
2. Add the lines of code shown below to webpack.config.js, then apply the configuration for every component at that level where a package.json file exists. The entry point is a Component/src/index.js file, and the main value inside the component (ex.: dist/index.js) is a package.json.
3. Add commonjs to treat all bundles independently.
4. Bundle each component by featuring the below script to package.json file at the components level. The config path varies depending on the library structure.
5. Run the lerna exec npm run \"build\" command in the terminal to build the script for each existing package.json file, except the root.
After taking steps, the structure should look like the image below.
Preparing to publish
Before publishing a component, it is necessary to specify a registry. Inside the root of the project, create a .npmrc file, and add registry=https://registry.npmjs.org/. No additional action is needed to publish component packages into a public registry or to configure Lerna. However, when publishing scoped packages, add the following line of code in each component package.json file to let npm know what type of registry to target.
To publish components to a private registry:
1. Create a specific token to a .npmrc file provided by the npm registry.
2. Remove the publishConfig option from the package.json files inside the components.
3. Add the below line of code to lerna.json config to track scoped projects as private.
4. Run a npm pack command inside the directory of your component via terminal. To see how a component looks in a registry before publishing. An archive with all the files is then included in the package.
5. Be sure to clean up after the check. Remove the archive and if applicable, extracted files.
As a result, the structure should look like this:
To publish components, link them with a lerna bootstrap command and then publish them with a lerna publish command. Then, follow the publish command steps inside the terminal, including creating a new commit, bumping packages versions, adding git tag, and finally publishing packages into the npm registry.
Creating a shareable components library
Taking the steps noted in this article is a widely common way how to build a shareable components library. The first-time setting this up may take a while. Once the code is finished and set, running one line of code and the same process applies to every component not only generates consistent results, the process saves developers time. When addressing a lot of components in the single library, the codebase size over time ultimately grows bigger and bigger, resulting in more idle time while every single script runs through every single component.
In the end, learning to create a library saves time for future projects, limits the need for future manual work maintaining the library and to build some components creation from scratch.
Pulling this all together, view a demo of React components library at https://github.com/audriusnavickasDB/components-library.
- For configuration information on Lerna, go to https://github.com/lerna/lerna#concepts.
- For more on semantic versioning, go to https://semver.org/.
- For more on dependencies and package.json, go to https://docs.npmjs.com/files/package.json.
- For more information about Storybook webpack configurations, go to https://storybook.js.org/docs/configurations/custom-webpack-config/.