DEV Community

Cover image for Publishing and reusing React components with TypeScript and Emotion
Dorival Pedroso
Dorival Pedroso

Posted on

Publishing and reusing React components with TypeScript and Emotion

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.

Nonetheless, it is difficult to convert the components that use 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 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

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:

"build": "rm -rf dist && mkdir -p dist/rcomps && rsync -av src/components/* dist/rcomps --exclude __stories__ --exclude __tests__"
Enter fullscreen mode Exit fullscreen mode

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:

"dist": "yarn build && cp package-dist.json dist/package.json && cd dist/ && yarn publish --access=public || true && cd .."
Enter fullscreen mode Exit fullscreen mode

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:

{
  "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"
}
Enter fullscreen mode Exit fullscreen mode

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

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:

"postinstall": "bash ./scripts/npm_postinstall.bash"
Enter fullscreen mode Exit fullscreen mode

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

The auxiliary npm_postinstall.bash script is:

#!/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
Enter fullscreen mode Exit fullscreen mode

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):

yarn add @cpmech/rcomps
Enter fullscreen mode Exit fullscreen mode

EmotionJS and React 17

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:

  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;
    }
  `;
Enter fullscreen mode Exit fullscreen mode

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:

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
Enter fullscreen mode Exit fullscreen mode

And then use like this:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

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 :-)

Top comments (0)