Writing your first React UI Library - Part 4: Ship It! (V1)
Tags: react, javascript
Published: 2020-06-16 20:53:51

![ship it](https://res.cloudinary.com/carlosdev/image/upload/v1592103960/react-lerna-css/shippingIt_o5gjft.png)

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

## What are we going to do?

- Map our compiled Javascript for older and newer clients in our package.json.
- Change a little bit the configuration of storybook and our builder to support compiling from the source.
- Publishing with Lerna!

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

1. Running `npm run build` at the root should make a build of all your components with `cjs` and `esm` formats output in a `dist folder.`
   ![file output](https://res.cloudinary.com/carlosdev/image/upload/v1590897130/react-lerna-css/output_sesh0j.png)

2. Running `npm run storybook` should start your dev kitchen sync.

3. CSS Modules are working in storybook and you can see the css on the compiled files as well.

### Mapping our compiled files in package.json

We have two types of clients for our UI Library:

1) People that just want an `it just works™` by just importing our components and forgetting about them; They will get our compiled components + css which won't clash with their styles for the most.

2) People deemed as `power users` which have their own bundling system and they want to generate their classes on their build process.

For this we'll modify the package.json in all our distributable packages to:

`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://webpack.js.org/) will use `module` entry when using `imports/exports` on an ES6 environment and `main` when we are using `require`.

We want those to be resolved from the compiled version in case our clients don't have CSS Modules on their app and they just want to use our components.

Notice We added a `src` attribute, this is basically a pointer to the real source that we want our `power users` to use.

Before we can proceed, we also need to add the `dist` folder to the files we are publishing to NPM; This can be done by adding the folder name into the `files` array in each package.json. For example this is the modification in the `phoenix` package.

`phoenix/package.json`

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

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


### Fix Storybook setup

The problem now, is that when running `storybook` it will grab the code pointing to `module` since this is the default webpack configuration behavior.

See here: https://webpack.js.org/configuration/resolve/#resolvemainfields


We don't want that since our kitchen sink should always point to latest `src` so we can try new things without having to run `build` on every change;

Let's change that:

`.storybook/main.js`

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

    // push our custom easy one
    config.module.rules.push({
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            // Key config
            modules: true,
          },
        },
      ],
    });
    // This is where we change the order of resolution of main fields
    config.resolve.mainFields = ['src', 'module', 'main'];

    // Return the altered config
    return config;
  },
};
```

With the above we are telling the storybook webpack to first grab `src` and if it doesn't find it, then fallback to the other options. This is the same configuration we are going to ask our `power users` to use when compiling the components on their own.

### Fix the builder setup

We also need to modify our `phoenix-builder` to grab the code from `src` instead of `main` as we had before.

`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();
// Little refactor from where we get the code
const { src, name } = require(path.join(currentWorkingPath, 'package.json'));

// build input path using the src
const inputPath = path.join(currentWorkingPath, src);

// Little hack to just get the file name
const fileName = name.replace('@cddev/', '');

// see below for details on the options
const inputOptions = {
  input: inputPath,
  external: ['react'],
  plugins: [
    resolve(),
    postcss({
      // Key configuration
      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() {
  // create bundle
  const bundle = await rollup.rollup(inputOptions);
  // loop through the options and write individual bundles
  outputOptions.forEach(async (options) => {
    await bundle.write(options);
  });
}

build();
```

### We are now ready to publish
Run
```sh
lerna publish
```
This will open a prompt in your terminal to select the version you want to publish.

We started in version `0.0.0` and since this is our first release, let's select Major. This will present a message on what it's going to happen:

```sh
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
```

**Run it!**

If everything goes well, you should see:

```sh
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
![Carlton](https://media.giphy.com/media/WmkqburJqXziM/giphy.gif)

## How can your clients consume it?

The beauty of this setup is that your clients can either consume the main package `phoenix` which will get them all the components or each component separately. Here are some examples:

### Consuming as a whole

```sh
npm i --save-dev @cddev/phoenix
```
And then later in your JS

```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 later in your JS

```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 hopefully have a small pipeline in terms of UI Development.

In future parts we will explore adding tooling such as `eslint`, `stylelint`, `prettier` to have a consistent codebase and prevent small bugs; We are also going to put in place a 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