From component library to design system
Development of UI components is one of the most cost-intensive tasks in front end development. To optimize processes, some companies entrust it to a dedicated team. We decided to take another way: to set up a library so that components could be added by developers from different teams. In this article, I will tell you how our library is organized.
Component library tasks
The company where I work creates several different products. Each has its own team, but the interface is designed in the same style.
The library is needed to avoid double work and reuse common elements. Here are the basic requirements to the library:
- Support of different versions.
- Simple adding of components.
- Combining the component code with visual requirements to the component.
I will tell you in detail below how we have implemented these requirements. In short, we used Verdaccio, Stencil, and import of svg files from Figma.
“Let us create two libraries instead of one”
Several years ago, we started writing a new interface for the entire ISPsystem line of products. We started redesigning with the billing system, and when another project was about to begin, we thought about reusing the written code. That is when the idea of the component library came up.
One of our developers was inspired by the idea of web components, not so well known at that time, and popularized the idea among us. The idea was excellent indeed: web components could be used in any web application and any framework. We were just starting to use Angular and we were not sure (or the thought of having a choice was warming our hearts) that we would create a new project in it. Therefore, we decided to make two libraries: one for web components and one for Angular. We called the first one ispui, and the second — ngispui.
We have decided that components from ispui will be used anywhere, both on the website and in projects on different frameworks; and ngispui will consist of wrappers for web components, for ease of use in Angular projects.
Our bright idea was crushed by the harsh reality. Writing on clean web components is not as convenient or fast as on Angular, so we did not develop them. In addition, it was not very convenient to support three projects (main, plus two libraries). We added a couple of web components and a few CSS components to ispui, and left them like that, while focusing on ngispui components.
This is how we started mainly working on the library for Angular. Over the time of its existence, we added over 50 components to the library. And it really paid off: new projects were launched in less than a year, while the first ones took almost two years.
Component library for Angular: how it works
The first version of the library was published in Git. We presented its designa year ago. The scheme was workable, but not perfect: all versions were fixed, so you could not take advantage of different versions support. Consequently, problems were encountered when building Angular if packages from the library had the same dependent package but different versions.
We were advised to use a local npm register of verdaccio packages. We deployed it locally, with authorization through GitLab. Life became easier: we could use all the features of different versions support. The publication script was rewritten.
When publishing in verdaccio, we take into account the branch from which the publication takes place; if it is not master, then at the time of publication we add the suffix "dev-current date and time" (for example 1.0.0-dev12.12.2019). In package.json at the repository, the version remains without a prefix. We identify the version and write the changelog manually. And when a branch with a new version of the component is merged with the master, CI starts publishing of the stable version. Nothing at that point needs to be corrected in the source code, which is convenient.
The requirements for adding components to the library are simple; they include tests + demo page. To quickly add components to the library, we have a plop script that generates a demo page template and adds it to the demo application. The developer only needs to make an example of a UI component with API description.
It turns out that a product developer needs to invest minimum effort to add a component to the library. Still, it is a little harder than just making a component inside a product.
Sometimes merge requests are left hanging in the air: the programmer accomplishes a product task and takes on the next one — there is no time to file a merge request to the library. We have to make arrangements with managers to allocate time to programmers for shared libraries, as this helps other teams and benefits everyone.
Each components gets its own version
The library is designed as a single application with a demo, but each component is published separately. That is, each component is a separate package with its own versions.
Individual publishing of components allows for greater flexibility. You can quickly develop a separate component, and disrupt backward compatibility without the concern of affecting another team if it needs to use a new component from the library. Another team can easily use the old version of one component, together with the new version of another. We develop new products, and the speed of first release and delivery of new features is important to us.
Aside from the pros, this approach has its cons. It turns out that when you publish one component, you need to:
- collect the entire library, and pass all the tests;
- if there are dependent packages, add them to the demo application;
- specify component dependencies in package.json.
Dependencies are set in the demo application and do not affect the build and publication of the component, so it is easy to forget about them. However, if you do not specify them, it will damage the build of the product application.
Building a component demo version for fast review
When a merge request is filed, the new component must be reviewed by a different developer and designer. Whereas the developer can build the application, there is no sense in it for the designer. To allow the reviewer to have a look at the code and at how it looks in the browser, each merge request must have a link to the component's demo version.
We have also automated building of the demo version: at the merge request a task is launched in CI, which builds the demo application and loads it into a folder on the web server with nginx. The folder is created according to the branch name, which allows to store multiple demo versions of the component simultaneously. The developer gets a ready link and adds it to the description of the merge request.
Turning the library into a design system
We wanted to create a single library for designers and developers, so that everyone could see in one place what components we have, what they look like in their final form, and how to use them. However, the resources for library development have always been limited, so we came up with a fast solution.
Designers created Figma pages with component descriptions. We agreed on a uniform naming approach. At present, when building the library, we simply download pages from Figma and insert them as SVG files into the demo page with components. This solution is not perfect: in the SVG image you cannot select the text or search the page, but you can read it.
When, in addition to the code, the library acquired design requirements and rules, it became a full-fledged design system. Now both developers and designers use it. Here is what it looks like:
Calculating how often a component is used
There are four teams working with the component library. Each team can make changes, and it is fraught with danger: you can damage something for someone — intentionally or not.
To avoid this, we decided to use statistics. We assumed that if a person knows how many people use a library component besides him, he will be more careful. Or won’t, if the component is not used by anyone else.
We asked a group of our interns to develop a service for gathering statistics on the use of library components. The team created a service consisting of several parts:
- the first script picks up the details on dependencies from package.json;
- the second script parses html for use of UI components and attributes of these components;
- the widget component displays the collected statistics.
Updating the library to the new Angular version
The Angular-based component library turned out good: it has many components, functional infrastructure and clear operation logic. However, it has its raw spots. One of these is update to the current version of Angular.
We have taken it as a rule to use the latest developments and the most current versions of the libraries, so when a new Angular is released, we try not to delay the update. But the library is used by all teams, so it must be updated synchronously — usually we agree that we will do it within a month. Otherwise, it is hard to develop common components, when everyone is using a different version.
In addition, the process of updating and publishing 50+ components is hard, even taking account automation. It takes up time, which could be spent on something more useful and interesting.
Reviving the library of web components
When the basic functionality of the new products was finished, it was time to add features for which Angular was no longer enough. There was a new task — plugins on the front end. The idea of developing them on web components and embedding them in the Angular application appeared. This way we will not be tied to the Angular version nor will we need to rebuild the plugins when the application is updated. The idea is great, but the development of plugins requires UI components.
This is what made us return to the library of web components — ispui. To avoid writing on clean web components, we started looking for libraries and frameworks that could simplify our task. Angular Elements was dismissed immediately, due to its association with Angular version, and the implementation itself is still raw for use as a library. LitElement is pretty simple, but at that time it was not Typescript compatible, and we like Typescript. Our guys wrote their own class for writing AbstractElement web components in their free time, but we do not have resources for its development or support, and it is not an option to outsource it. Meanwhile, we were taken over by Stencil: support of Typescript, TSX, Angular-like syntax with decorators, ready-made ecosystem with tests, documentation generation and loader for use in Angular and other frameworks.
We are now actively developing the ispui library, taking into account previous experiences and wishes. What we have done already:
- A monorepository managed by lerna. You can use it to launch building, testing and publishing of individual packages.
- Each component has its own demo in the form of an html file. During building, it is added to the demo application.
- Documentation is generated for stencil components, which is inserted into the demo application with a description of the component API and the CSS variables used.
- To automatically update versions and generate changelog, we use writing commits according to the commits convention. Git-cz utility is useful for this purpose
Summary: how to develop a design system, when the resources are limited
The project is under active development, but it is already possible to summarize the interim results. This is what you should pay attention to if you need a design system but the resources are limited.
- Use ready-made infrastructure management tools. For example, lerna.
- Use automatic listing, configured tslint/eslint, stylelint, commitlint. They allow automatic project validation.
- Automate routine processes. E.g., demo page creation.
- Use frameworks and libraries with a well-developed infrastructure and a configured test environment, as well as automatic generation of documentation.