Publishing and reusing React components with TypeScript and Emotion
Tags: react, typescript, npm, emotion
Published: 2021-02-02 05:04:01

# Introduction

Usually, we should not publish TypeScript code to the NPM database, except for the typings. The normal approach is to convert the code into CommonJS (cjs) and/or ES modules (esm) and then publish. An easy way to do this is with [RollupJS](https://github.com/rollup/rollup).

Nonetheless, it is difficult to convert the components that use [Emotion](https://github.com/emotion-js/emotion) to reusable modules. Moreover, _why not making our TypeScript code directly available?_ (aiming at reusing it later in a React application).

Here, we explain our approach to publish a bunch of _simple-but-powerful_ [React 17](https://reactjs.org) components, written in TypeScript and with Emotion, that we use in a number of projects (e.g. https://epop.net.br).

The code is available on GitHub: https://github.com/cpmech/rcomps

## Contents

* [Publishing TypeScript components](#chapter-1)
* [Reusing TypeScript Components](#chapter-2)
* [Emotion and React 17](#chapter-3)
* [Conclusion](#chapter-4)

# Publishing TypeScript components <a name="chapter-1"></a>

**NOTE:** This procedure uses `yarn`, `rsync`, and `jq`.

The idea is quite simple. First, copy all the `.ts` and `.tsx` files to the `dist` directory when calling `yarn build`. To do so, add this command to `package.json`:

```json
"build": "rm -rf dist && mkdir -p dist/rcomps && rsync -av src/components/* dist/rcomps --exclude __stories__ --exclude __tests__"
```

Above, we remove any existent `dist` directory, create a subdirectory named `dist/rcomps`, which will help the consumer of this library to move it to the right place, and then copy the files using `rsync` (ignoring storybook and jest files).

Second, for the `yarn dist` command, copy an alternative package-dist.json file to the `dist` directory by using this `package.json` command:

```json
"dist": "yarn build && cp package-dist.json dist/package.json && cd dist/ && yarn publish --access=public || true && cd .."
```

Above, we call the build command, copy the two package-json files into dist, and then call `yarn publish`. 

The alternative (distributable) `package-dist.json` is:

```json
{
  "name": "<YOUR NPM PROJECT HERE>",
  "version": "0.1.0",
  "license": "MIT",
  "scripts": {
    "fix:ver1": "jq '.version = $newVal' --arg newVal `jq -r '.version' package.json` ../package.json > tmp.$$.json && mv -f tmp.$$.json ../package.json",
    "fix:ver2": "jq '.version = $newVal' --arg newVal `jq -r '.version' package.json` ../package-dist.json > tmp.$$.json && mv -f tmp.$$.json ../package-dist.json",
    "git:add": "git add ../package.json ../package-dist.json",
    "git:tag": "git tag v`jq -r '.version' package.json`",
    "git:all": "yarn git:add && git commit -m 'Publish' && yarn git:tag && git push && git push --tags",
    "postpublish": "yarn fix:ver1 && yarn fix:ver2 && yarn git:all"
  },
  "repository": "<YOUR GITHUB REPO HERE>",
  "description": "TypeScript React Components with Emotion"
}
```

The above code will automatically tag the git repository and fix the version numbers in both the main package.json and the publishable (alternative) package-dist.json. Each sub-command is explained below:

* `fix:ver1` fixes the version in the parent package.json file
* `fix:ver2` fixes the version in the distributable package-dist.json file
* `git:add` takes the modifications in package.json and package-dist.json
* `git:tag` creates a new tag based on the updated version in package.json
* `git:all` runs "all" git commands, including adding, tagging, committing, pushing, and pushing tags
* `postpublish` is the hook, called automatically by `yarn publish`, that will call the above sub-commands.

# Reusing TypeScript Components <a name="chapter-2"></a>

To reuse the TypeScript components, we have to copy the `.ts` and `.tsx` files from `node_modules` to the right place (e.g. the `src` directory of your `create-react-app` project). To do so, we add the following command to the scripts section of the **app's package.json**:

```json
"postinstall": "bash ./scripts/npm_postinstall.bash"
```

The above command will be automatically called whenever we run `yarn install`.

The auxiliary `npm_postinstall.bash` script is:

```bash
#!/bin/bash
set -e
if [[ -d "./node_modules/@cpmech/rcomps/rcomps" ]]; then
    echo ">>> moving rcomps to src <<<"
    rm -rf ./src/rcomps
    mv ./node_modules/@cpmech/rcomps/rcomps ./src/
fi
```

The above script simply removes any existent `./src/rcomps` directory from your React app and copy it again from the `node_modules`. **NOTE:** we have to move the `rcomps` directory from `node_modules` because `react-scripts start` or `react-scripts test` may complain if they find TypeScript files inside `node_modules`.

Now, you can simply (for example):

```bash
yarn add @cpmech/rcomps
```

# EmotionJS and React 17 <a name="chapter-3"></a>

[Emotion](https://github.com/emotion-js/emotion) is fantastic! It allows you to write _real_ CSS to style your component. So, everything is possible! For example, this is a piece of CSS for our HTML input element [see our inputElementCss.ts file](https://github.com/cpmech/rcomps/blob/master/src/components/foundation/inputElementCss.ts):

```typescript
  const common = `
    position: relative;
    height: ${height}px;
    margin-top: ${marginTop}px;
    width: ${width};
    input {
      font-size: ${fontSize}px;
      box-sizing: border-box;
      height: ${height}px;
      width: 100%;
      padding-left: ${paddingHoriz}px;
      padding-right: ${pickerMode ? paddingRightPicker : paddingHoriz}px;
      border: ${borderWidth}px solid ${borderColor};
      border-radius: ${borderRadius}px;
      ${flatLeft ? `border-top-left-radius:0;border-bottom-left-radius:0;` : ''}
      ${flatRight ? `border-top-right-radius:0;border-bottom-right-radius:0;` : ''}
      color: ${textMode ? mutedColor : color};
      background-color: ${bgColor};
      resize: none;
      outline: none;
      ${pickerMode && !textMode ? `cursor:pointer;` : ''}
      ${
        pickerMode
          ? `text-overflow: ellipsis;
             white-space: nowrap;
             overflow: hidden;`
          : ''
      }
    }
    input[required] + label[placeholder] {
      display: block;
      pointer-events: none;
      line-height: ${labelFontSize}px;
      margin-top: -${deltaLabel}px;
    }
  `;
```

Note above the use of `input[required] + label[placeholder]` that we cannot do with standard React.

To use EmotionJS with React 17, where we don't need to import React anymore, simply add the following line to the top of your TypeScript code:

```typescript
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
```

And then use like this:

```typescript
export const RcProgress: React.FC<RcProgressProps> = ({
  progress,
  color = '#ffffff',
  backgroundColor = '#e5e5e5',
  borderColor,
  barColor = '#4d50c6',
  borderRadius = 300,
}) => {
  const p = progress || 0;
  const width = p < 0 ? 0 : p > 100 ? 100 : p;
  return (
    <div
      css={css`
        ${backgroundColor ? `background-color: ${backgroundColor};` : ''}
        ${borderColor ? `border: 1px solid ${borderColor};` : ''}
        border-radius: ${borderRadius}px;
      `}
    >
      <div
        css={css`
          height: 24px;
          width: ${width}%;
          color: ${color};
          font-weight: bold;
          font-size: 15px;
          line-height: 1.5;
          background-color: ${barColor};
          border-radius: ${borderRadius}px;
          text-align: center;
          padding-top: 1px;
        `}
      >
        {width > 3 && `${width}%`}
      </div>
    </div>
  );
};
```

# Conclusion <a name="chapter-4"></a>

The development process with TypeScript and React 17 is quite enjoyable these days. The create-react-app makes the bootstrapping process much easier (e.g. `yarn create react-app myapp --template typescript`). We also suggest using EmotionJS for styling the React components because we can do much more than with standard React. Finally, it is **possible and convenient** to make your TypeScript Components directly available on NPM.

[PS: check out our components :-)](https://cpmech.github.io/rcomps)