At Drivy, we have defined our very own design system. This system describes our visual guidelines and rules, and is composed of visual web components. For each component, we have created a React implementation that could easily be used by our design team to build a web site documentation (thanks to MDX).
Having a documented design system was an achievement in itself. But to fully take advantage of it, the final step was to use our React components in other frontend projects.
In a Node.js world, that means importing them as a node module dependency.
The main steps are:
Key points of the design system project to bundle:
Notice that some of the sources have a specific syntax (tsx/ts files) and have to be transformed (transpiled) to be read by a browser. Bundling process must produce outputs that can be seamlessly imported in a tier project without extra configuration or processing.
We chose rollup.js as bundler tool for our library because it is well adapted: it’s efficient and easy to configure. Other module bundlers like Webpack and Parcel provide advanced features for a developer (dev-server with hot module replacement, for example) but those things are not required for our achievement.
We want the following outputs:
Rollup is configured thanks to a rollup.config.js
file at the root of our project.
Rollup has a bunch of plugins. For our needs we use:
Note: currently, we don’t use a plugin to convert our Sass files to CSS ones. It’s a deliberate choice as we want to output our design system variables and mixins to use them in other projects using Sass. But it would be great if we could support both Sass and CSS, in order to stick with our “ready-to-use” principle. So we’ve scheduled this task in our roadmap.
Rollup’s config requires an entry point that will be used to resolve the dependencies
This file will export all our components
Then we define how the build result should be output
Our project is based on React and so uses some external dependencies (react, react-dom, classnames …). We don’t want such libraries to be resolved and bundled with our project. So, all dependencies external to the project (described in package.json dependencies/peerDependencies) are configured to be excluded from the build process
Our project being set up with TypeScript, we want to export our declarations files (*.d.ts) so our lib API will be smoothly consumed (available) by any other TypeScript project.
We have 2 options:
hello.js
module, TypeScript will look for a hello.d.ts
file in the same directory.package.json
Example:
We recommend the latter. It allows you to separate the types (TypeScript related) from your JavaScript code source
To obtain this result, you can configure the TypeScript compiler (tsconfig/json) to generate the declaration files in a dedicated directory
Then you can configure rollup to use this setting for its TypeScript plugin
We launch rollup to build our project:
The result is available in the dist folder (as defined in the rollup output config)
Note:
Our full build script launches rollup then copy our package.json
to the dist folder.
We are now ready to publish our package to NPM so it will be available as a dependency to another project (using npm install / yarn add). NPM has different mechanisms to specify the files to package and publish:
These 2 approaches are somewhat different, and one is not necessarily preferable to the other. However, the blacklisting strategy tends to be riskier as it exposes files that are sensitive or not relevant. Keep in mind that some files are always included, regardless of settings:
In package.json
:
Tip:
It’s not easy to visualize files that are packaged by NPM (npmjs.com doesn’t list files of a module)
To do so with your local project, execute the following command in the directory containing your package.json
file:
It will output the files that will be included in the publishing process
When importing our components in a tier-project and using them in a real context, we can often encounter conflicts (from tier css rules for example) and unintended behavior, or simply discover some bugs.
We can’t afford to wait for our components to be published to encounter those issues.
We must have a way to test our components in a tier project before publishing to NPM
We can use the npm/yarn link mechanism to symlink our component project and add it to the node_modules of another one.
However, such a mechanism doesn’t allow us to perfectly reflect how our project will be packaged by NPM and deployed in another project. Plus, some configuration files that would not be present in the node modules (filtered by the NPM publishing process) can interfere.
To be closer to the real process (how our package will be published and installed), we used an advanced tool named yalc
.
yalc
allows to package a project and add it as a node module like NPM would do, but in a local store.
We can now use our design system assets in any frontend projects. There are natural advantages that come with using a centralised library: single source of truth, reduced maintenance cost, etc. But using a centralised library also enables us to invest more in our design system, which in turn makes it easier for the design system to be adopted company-wide.