Building and Publishing Design Systems | Part 2
This post is part of the "Building design system and micro frontends" series. See the first post for context. Source code for all the parts of this series can be found in this GitHub repository.
1. Building design system and micro frontends
2. Building and publishing design system (you are here)
3. Building micro frontend consuming design system
4. Building shell application for micro frontends
For building our design system, we will use react, tailwind, storybook and webpack.
A few words why I selected this technologies:
React - proven, stable, excellent component composition system, focus on back-compatibility. Read the "Design Principles" post on reacts blog; this post outlines react development guiding principles.
Tailwind - gives us a simple way to define seed for the design system. Sizes/Distances scale, colour palettes, shadows, responsive breakpoints, transitions and animations etc., all can be defined succinctly. It also gives us ultimate style isolation and a big productivity boost.
Storybook - helps us develop components in isolation. Helps in component testing and, when exported, delivers portable documentation.
Webpack - proven, stable, vivid plugin ecosystem; a lot of materials.
Setting up Design System Project
Create directory design-system
and run the following commands from it:
npm init -y
will setup package.json
file.
Run npm run storybook
to build and run storybook. It should bind to port 6006
. Open http://localhost:6006
. You should see some example stories.
The npx sb init
command takes care of setting up the storybook. It will create a stories
directory with examples in the project root directory. You can remove its content. We will create a simple story later from scratch.
Set up tailwind
npm i -D tailwindcss@2.2.4 postcss@8.3.5 npx tailwindcss init
npx tailwindcss init
will create ./tailwind.config.js
file with the default configuration.
Open ./tailwind.config.js
Add mode
property and set purge
patterns (lines 2,3).
module.exports = { mode: "jit", purge: ["./src/**/*.js", "./src/**/*.html"], darkMode: false, // or 'media' or 'class' theme: { extend: , variants: { extend: , plugins: [], }
Create ./postcss.config.js
in root directory.
Paste following code in ./postcss.config.js
:
module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} }
This configuration file adds tailwindcss
and autoprefixer
plugins to PostCss processing pipeline.
Setup tailwind for storybook
In the next two steps we will setup tailwind for storybook. We need to install appropriate plugin and configure it.
Install @storybook/addon-postcss
addon for storybook.
npm i -D @storybook/addon-postcss@2.0.0
Update ./.storybook/main.js
; add highlighted configuration to addons
array:
module.exports = { stories: [ "../stories/**/*.stories.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)", ], addons: [ "@storybook/addon-links", "@storybook/addon-essentials", { name: "@storybook/addon-postcss", options: { postcssLoaderOptions: { implementation: require("postcss"), }, }, } ], core: { builder: "webpack5", }, };
Create file ./src/styles.css
.
Paste following code in ./src/styles.css
:
@tailwind base; @tailwind components; @tailwind utilities;
Open ./.storybook/preview.js
and add import styles (line 1).
import '../src/styles.css' export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, }
Create component and story
Create Button.js
file in ./src/components
with the following content
import React from "react"; export default function Button({label, disabled, children}) { return ( <button className={` inline-flex items-center px-4 py-2 text-white text-base font-bold bg-blue-600 border border-transparent rounded-sm hover:bg-blue-500 hover:shadow-sm disabled:bg-gray-200 disabled:hover:shadow-none disabled:cursor-not-allowed disabled:text-gray-400 focus:outline-none focus:ring-2 focus:ring-offset-2 `} disabled= ></button> ); }
In ./stories
directory create `Button.stories.js` file with the following content:
import React from 'react'; import Button from '../src/components/Button'; export default { title: 'Example/Button', component: Button, argTypes: { label: {control: 'text'}, disabled: {control: 'boolean'} }, }; const Template = (args) => <Button />; export const Primary = Template.bind({}); Primary.args = { label: 'Button', disabled: false }; export const DisabledButton = Template.bind({}); DisabledButton.args = { label: 'Button', disabled: true };
Run npm run storybook
. Open http://localhost:6006
. You should see something like this:
Building design system package
It is time to create production build of our design system. It should contain only necessary code. We will exclude react from the build. The assumption here is that whoever is consuming design system will be responsible for including react.
In a real project design system should be packaged and published to a npm
repository so the other project can use npm
to get it. However, for this walkthrough, we will build it and keep it in a directory on the disk. Other projects that we build in the next post will consume it from the disk.
Create an entry point for our library; Create ./src/index.js
and add the following content:
import './styles.css' import Button from "./components/Button"; export
Every component in our design system must be imported and re-exported in this file.
Install following packages:
npm i -D webpack@5.38.1 webpack-cli@4.7.2 @babel/preset-env@7.14.4 @babel/preset-react@7.13.13 babel-loader@8.2.2 cross-env@7.0.3 css-loader@5.2.6 mini-css-extract-plugin@2.1.0 css-minimizer-webpack-plugin@3.0.2 postcss-loader@5.3.0 style-loader@2.0.0
Create file ./.babelrc
with the following content:
{ presets: [ [ '@babel/preset-env', { modules: false } ], '@babel/preset-react' ], plugins: [] }
Create file ./webpack.config.js
with the following content:
const path = require('path'); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const config = { entry: [ './src/index.js' ], output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js', library: { name: "fitness", type: "umd" } }, externals: { "react": "react", "react-dom": "react-dom" }, module: { rules: [ { test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/ }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader' ] } ] }, resolve: { extensions: [ '.js', '.jsx', '.css' ] }, plugins: [ new MiniCssExtractPlugin({ filename: '[name].css' }) ], optimization: { minimizer: [ new CssMinimizerPlugin() ] } }; module.exports = (env, argv) => { config.output.filename = '[name].js'; return config; };
There is a lot of things going on in this file; so lets break it out:
Line 6 - we define an entry point for our library, all components exported in this file will be available for consumers.
Line 9 - the output of the build will be saved to
dist
folderLines 11-14 - we configure webpack to create a library and use UMD for modules.
Lines 16-19 - we externalize react. This way it is not packaged with the library.
Lines 20-41 - standard configuration for building js, jsx and CSS
Lines 42-46 - add plugin for CSS extractions
Lines 47-52 - configure CSS minification
The last step is to add npm
script; add following line to package.json
scripts
array:
"build-design-system": "cross-env NODE_ENV=production webpack --mode production"
To build the design system run npm run build-design-system
.
There should be two files in the dist
directory. main.js
(5KB), and main.css
(7KB).