# Writing your first React UI Library - Part 4: Ship It! (V1)

Map compiled library outputs, finish the Storybook and builder setup, and publish with Lerna.

- Author: Carlos Castro
- Published: June 16, 2020
- Series: [Writing your first React UI Library](https://www.carloscastro.dev/articles.md), part 4 of 4
- Previous: [Writing your first React UI Library - Part 3: CSS Modules](https://www.carloscastro.dev/articles/writing-your-first-react-ui-library-part-3-css-modules.md)
- Originally published at https://dev.to/davixyz/writing-your-first-react-ui-library-part-4-ship-it-v1-3pa8

> Originally published June 16, 2020. This tutorial reflects the versions and workflows used at publication; some packages and commands may have changed.

![Shipping the React UI library](https://www.carloscastro.dev/assets/articles/react-ui-library/part-4-shipping-it.png)

This is the fourth post in a series on how to make your own React UI Library.

## What are we going to do?

- Map our compiled JavaScript for older and newer clients in `package.json`.
- Change the Storybook and builder configuration to compile from source.
- Publish with Lerna!

## Shipping it!

By now you should have almost everything ready to ship it:

1. Running `npm run build` at the root should build all your components in CJS and ESM formats in a `dist` folder.

   ![Compiled package output](https://www.carloscastro.dev/assets/articles/react-ui-library/part-4-build-output.png)

2. Running `npm run storybook` should start your development kitchen sink.

3. CSS Modules should work in Storybook and appear in the compiled files.

### Mapping our compiled files in package.json

We have two types of clients for our UI Library:

1. People who want an “it just works™” experience by importing components and forgetting about them. They get compiled components and CSS that should avoid most style clashes.
2. “Power users” who have their own bundling system and want to generate classes during their own build process.

For this, modify the `package.json` in all distributable packages.

`phoenix/package.json`

```json
"main": "dist/phoenix.cjs.js",
"module": "dist/phoenix.esm.js",
"src": "lib/phoenix.js",
```

`phoenix-button/package.json`

```json
"main": "dist/phoenix-button.cjs.js",
"module": "dist/phoenix-button.esm.js",
"src": "lib/phoenix-button.js",
```

`phoenix-text/package.json`

```json
"main": "dist/phoenix-text.cjs.js",
"module": "dist/phoenix-text.esm.js",
"src": "lib/phoenix-text.js",
```

Modern bundlers like [Webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/) will use the `module` entry for ES module imports and `main` for `require`.

We want those to resolve from the compiled version in case clients do not have CSS Modules configured in their app and simply want to use our components.

Notice that we added a `src` attribute. This is a pointer to the real source that “power users” can compile themselves.

Before we proceed, add the `dist` folder to the files published to npm. For example, in the `phoenix` package:

```json
"files": [
  "dist",
  "lib"
],
```

Do the same for the `phoenix-button` and `phoenix-text` packages.

### Fix Storybook setup

When running Storybook it will grab the code pointed to by `module`, since that is the default webpack behavior. We do not want that: our kitchen sink should always point to the latest source so we can try new things without building first.

`.storybook/main.js`

```js
module.exports = {
  stories: ['../packages/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
  webpackFinal: async (config) => {
    config.module.rules = config.module.rules.filter(
      (rule) => rule.test.toString() !== '/\\.css$/',
    );

    config.module.rules.push({
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            modules: true,
          },
        },
      ],
    });

    config.resolve.mainFields = ['src', 'module', 'main'];
    return config;
  },
};
```

This tells Storybook to grab source first and fall back to the other entries.

### Fix the builder setup

We also need to modify `phoenix-builder` to grab code from `src` instead of `main`.

`phoenix-builder/lib/phoenix-builder.js`

```js
#!/usr/bin/env node
const rollup = require('rollup');
const path = require('path');
const resolve = require('@rollup/plugin-node-resolve').default;
const babel = require('@rollup/plugin-babel').default;
const postcss = require('rollup-plugin-postcss');

const currentWorkingPath = process.cwd();
const { src, name } = require(path.join(currentWorkingPath, 'package.json'));
const inputPath = path.join(currentWorkingPath, src);
const fileName = name.replace('@cddev/', '');

const inputOptions = {
  input: inputPath,
  external: ['react'],
  plugins: [
    resolve(),
    postcss({ modules: true }),
    babel({
      presets: ['@babel/preset-env', '@babel/preset-react'],
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
    }),
  ],
};

const outputOptions = [
  { file: `dist/${fileName}.cjs.js`, format: 'cjs' },
  { file: `dist/${fileName}.esm.js`, format: 'esm' },
];

async function build() {
  const bundle = await rollup.rollup(inputOptions);

  for (const options of outputOptions) {
    await bundle.write(options);
  }
}

build();
```

### We are now ready to publish

Run:

```sh
lerna publish
```

This opens a prompt to select the version. We started at `0.0.0`; since this is the first release, select Major.

```text
Changes:
 - @cddev/phoenix-builder: 0.0.0 => 1.0.0
 - @cddev/phoenix-button: 0.0.0 => 1.0.0
 - @cddev/phoenix-text: 0.0.0 => 1.0.0
 - @cddev/phoenix: 0.0.0 => 1.0.0
```

If everything goes well, you should see:

```text
Successfully published:
 - @cddev/phoenix-builder@1.0.0
 - @cddev/phoenix-button@1.0.0
 - @cddev/phoenix-text@1.0.0
 - @cddev/phoenix@1.0.0
lerna success published 4 packages
```

Congrats! Your library has been published.

![Animated celebration reaction GIF](https://www.carloscastro.dev/assets/articles/react-ui-library/part-4-carlton.gif)

## How can your clients consume it?

The beauty of this setup is that clients can either consume the main `phoenix` package, which gets all components, or each component separately.

### Consuming as a whole

```sh
npm i --save-dev @cddev/phoenix
```

And then in JavaScript:

```js
import { Button, Text } from '@cddev/phoenix';

render() {
  return (
    <>
      <Button>Woo</Button>
      <Text>Waa</Text>
    </>
  );
}
```

### Consuming one package only

```sh
npm i --save-dev @cddev/phoenix-button
```

And then in JavaScript:

```js
import { Button } from '@cddev/phoenix-button';

render() {
  return <Button>Woo</Button>;
}
```

## Conclusion

With this setup you should be able to add more packages, release them independently, and have a small UI development pipeline.

In future parts we will explore tools such as ESLint, Stylelint, and Prettier for a consistent codebase, plus testing infrastructure using Jest and React Testing Library.

For now, I leave you with a phrase so you can keep learning on your own: “In case of doubt, push on just a little further and then keep on pushing.”

## Resources

- GitHub repository with all code: <https://github.com/davixyz/phoenix>
- Demo Storybook: <https://davixyz.github.io/phoenix>
- GitHub: <https://github.com/davixyz>
- Twitter: <https://twitter.com/carloscastrodev>
