Making of a component library for React

March 24, 2018 0 Comments

Making of a component library for React

 

 

Github | NPMDemo
https://github.com/sasha240100/react-rectangle-popup-menu
Originally published at ZeoLearn.

Some time ago I decided to make an article that covers topics like publishing to NPM, API architecture. Best tutorial for this would be a react component library development.

A good example of React Component library as for me is react-color. It has neat documentation website with easy step-by-step guide and lots of different variations of making a color-picker for your React application.

https://casesandberg.github.io/react-color/

So I decided to go with making a rectangle-popup-menu like Google has:

This is from google.com website (an example of what I plan to make)

Let’s make up some requirements that every modern library should fit nowadays.

  • Tree-shaking
  • UMD pattern
  • Build output is es5
  • Clear documentation
  • Examples (demos)
  • Unit testing

Rollup & Webpack = Module bundling

Rollup & Webpack

For bundling library I chose rollup (you can read in a more detailed article that explains why rollup is better for libraries in “Webpack and Rollup: the same but different” by Rich Harris).

Rollup: https://rollupjs.org/guide/en

Plus I used webpack (and webpack-dev-server) to serve examples folder. It handles css wrapping, font loading and es6 -> es5 transpilation (with the help of babel).

Webpack: https://webpack.js.org/

Jest = Unit testing

Unit-testing tool by Facebook that perfectly fits our needs in comparing react component snapshots.

Link: https://facebook.github.io/jest/

Express = Serving examples

Has a minor place in a project, but helps us configure proper examples serving properly (as webpack-dev-server is using it).

Sample of code, where we use express to serve static files for development mode

Babel = write es6, provide es5

Nowadays ES6 is already widely supported by a majority of well-known browsers, but Babel is not just about working with es5, it’s a global standard for transpiling javascript subsets into browser-compatible implementation.

In this project we use Babel mostly for using latest ES features like async/await and supporting JSX subset.

Link: https://babeljs.io/

Now, once we installed all required devDependencies we are able to move forward to the development process.

The first step is to decide on the project folder structure. For this library, this is what I had at the end:

  • assets/ — Skip this folder, this is just for images I had in API docs
  • build/ — UMD and ES module build files + sourcemaps.
  • examples/ — This folder contains test .html examples that I use to test library in different cases.
  • src/ — Contains library source code written in JSX and .babelrc file for in-folder babel configuration
  • test/ — JEST tests

Other files are needed to complete project configuration. I highly recommend you to use travis.yml for Continuous integration (must-have for modern projects, especially such as a component library).

Important: Don’t forget to exclude react and react-dom from internal dependencies in rollup, otherwise, you’ll get compatibility errors on the client side and bad side effects, such as large bundle size and bad performance.
// rollup.config.babel.js
{
  // ...
external: ['react', 'react-dom'],
  globals: {
react: 'React',
'react-dom': 'ReactDOM'

}
}

In the index.js file I linked every single file that exports classes (actually React components):

// index.js
export * from './PopupMenu';
export * from './PopupTable';
export * from './PopupText';

We won’t cover all the code written for this library as it’s more topic-specific rather than helpful, so let’s take for example only several parts of the code that explain our needs in project organization.

PopupMenu header part

As we build components for UI we include style files, rollup allows us to make bundles with styles and images the same way as webpack does. In my case I chose sass syntax as it is a one I am familiar with. This means that ./PopupMenu.scssfile will be compiled from sass to css and then included as a javascript code that inserts inline styles into html <head> tag.

rollup-plugin-postcss module used in rollup config file.

Take into account that we enable "modules": true feature that enables CSS modules feature:

import style from './PopupMenu.scss';
// Somewhere in JSX
// ...
  render() {
return (<div className={style.myClassName} />)
}
// ...

Any time we use JSX syntax in our ES6 modules we need to include react dependency, otherwise it will result in a build error.

import React, {Component} from 'react';

In my case I even need to use React to provide context to child components this old way: (old because recently React introduced new Context API). I have PopupTable that takes popupWidth of the parent PopupMenu

React context in PopupMenu class

I would like to also highlight classnames npm module that does pretty good job in combining multiple classes from CSS modules:

classnames module example

Usage:

First you need to decide which unit-testing framework you want to choose. There are a few popular unit-testing frameworks I used and could suggest you to test:

For this component library I chose Jest. Because it is developed by Facebook (same as React) and it supports React components snapshot testing out of the box.

Capture snapshots of React trees or other serializable values to simplify testing and to analyze how state changes over time. (From JEST website)

That’s pretty simple, you render a react component you want to test, then call component.toJSON() which converts it to a rendered html tree. Example:

// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[PopupMenu should render correctly 1] = <br>&lt;div<br>  className=&quot;PopupMenu_PopupMenu__8TfA4&quot;<br>&gt;<br>  &lt;div<br>    className=&quot;PopupMenu_button__20m_Y&quot;<br>    onMouseOut={[Function]}<br>    onMouseOver={[Function]}<br>  /&gt;<br>  &lt;div<br>    className=&quot;PopupMenu_popover__3hd_Z&quot;<br>    onMouseOut={[Function]}<br>    onMouseOver={[Function]}<br>    style={<br>      Object {<br>        &quot;height&quot;: &quot;auto&quot;,<br>        &quot;left&quot;: &quot;calc(-100px + 50%)&quot;,<br>        &quot;visibility&quot;: &quot;hidden&quot;,<br>        &quot;width&quot;: &quot;calc(200px - 10px)&quot;,<br>      }<br>    }<br>  /&gt;<br>&lt;/div&gt;<br>;

Once you did it for the first time it is saved into a *.test.js.snap file, which is basically a module that exports several rendered DOM tree strings to compare with each new render. If the DOM tree has changed, it will tell you that. If the tree was changed by positive changes (means that was expected), you have to run jest -u to save new snapshots.

Let’s highlight the most relevant tips from this article:

  • Rollup is better than Webpack for component libraries
  • Include postcss plugin and bundle css into the same js file
  • Use Unit testing and continuous integration to show others that you care about stability and prevent unexpected errors.

I would also recommend you divide the build into two steps, development and production (Includes minification and further optimizations).

All the sources can be found on Github page. And the resulting demo is available as well.
Originally published at ZeoLearn.


Tag cloud