📦 Publish React npm Package with Rollup

rollup-typescript _2_.png

In this tutorial, I’ll walk you through my own process of using Rollup, a popular bundler, to create a React component package, enabling others to easily integrate your components into their projects.

Initialize the repo

Create a new directory called react-lib and initialize a new npm project within it, allowing you to install and manage dependencies for a React library.

1
2
3
mkdir react-lib
cd react-lib
npm init

Add dependencies

Add dependencies we need. And Styled-components is a CSS-in-JS library that allows to write CSS in JavaScript code. It provides a powerful API for building reusable and dynamic styled components in React applications.

1
2
npm i -D react typescript @types/react
npm i --save styled-components

TypeScript configuration

Initialize a TypeScript project by creating a tsconfig.json file which specifies how TypeScript should compile the project’s source files.

1
npx tsc --init

This is an example for tsconfig.json.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"compilerOptions": {
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "esnext",
"jsx": "react", /* Specify what JSX code is generated. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
"outDir": "./types", /* Specify an output folder for all emitted files. */
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src"
],
"exclude": [
"test"
]
}

ESLint configuration

ESLint is a popular linting tool for JavaScript that helps catch common errors and enforce coding standards, and it can also be configured to catch specific coding issues, such as incorrect syntax or anti-patterns, saving time and prevent bugs from slipping through the cracks.

1
2
npm i eslint -D 
npx eslint --init

Create .eslintignore to exclude certain files from ESLint checks.

1
2
3
test/**
lib/**
types/**

Add .gitignore

Create .gitignore to exclude certain files from git push.

1
2
3
/node_modules
/types
/lib

Adding components

Write and export your custom component code in the src folder. And here is an example in TypeScript.

  • src/components/Sum/index.tsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import React from "react";

    import { SumProps } from "./Sum.types";
    import StyledSum from "./styles";

    const Sum: React.FC<SumProps> = ({ a, b }) => {
    const sum = a + b;
    return <StyledSum>{a} + {b} = {sum}</StyledSum>;
    };

    export default Sum;
  • src/components/Sum/Sum.types.ts

    1
    2
    3
    4
    export type SumProps = {
    a: number;
    b: number;
    };
  • src/components/Sum/styles.tsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import styled from 'styled-components';

    const StyledSum = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem;
    font-weight: bold;
    padding: 10px;
    background-color: #f2f2f2;
    border: 1px solid #ccc;
    border-radius: 5px;
    `;

    export default StyledSum;
  • src/components/index.ts

    1
    export { default as Sum } from './Sum';
  • src/index.ts

    1
    export * from './components';

Rollup configuration

Rollup is a module bundler that allows to package your code into modules, making it easier to share and distribute code. It creates a single file from multiple ES6 modules, resulting in smaller output files with faster loading times, and makes it particularly useful for creating libraries and packages, and can improve web application load times, especially on mobile devices.

1
npm i -D rollup

Why Rollup

  • Rollup uses the ES6 standard format to bundle code.
  • It only bundles JavaScript, which makes it fast and generates small bundle sizes.
  • It has algorithmic advantages in handling pure code and is suitable for developing JavaScript libraries, it can also be used for bundling application development.

Why not

  • When code splitting is needed, and Rollup does not support code splitting.
  • If there are many static resources that need to be processed, and the module structure is complex.
  • If the project being built requires many dependencies on CommonJS modules.

Packages

  • @rollup/plugin-node-resolve: enables to use external libraries and modules. Rollup does not support directly bundling the contents of the node_modules folder, so it needs to be installed.
  • @rollup/plugin-commonjs: converts CommonJS modules to ES6 modules, enables to use modules that were not designed for ES6 module syntax. Lodash is not bundled by default as it uses CommonJS and Rollup only processes ES modules, but this package can solve the problem of CommonJS exports and ES module imports.
  • @rollup/plugin-typescript: compiles TypeScript files into JavaScript code.

And this is my rollup.config.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";

export default {
input: "src/index.ts",
external: ['react'],
plugins: [
typescript(),
commonjs(),
resolve()
],
output: [
{
file: "lib/bundle.cjs.js",
format: 'cjs',
},
{
file: "lib/bundle.esm.js",
format: 'esm',
},
{
file: "lib/bundle.browser.js",
format: 'umd',
name: 'Dry',
},
],
};

Add new scripts commands build and build:types to the package.json file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"name": "@bk/sum",
"version": "1.0.0",
"description": "A react component that sum two numbers.",
"repository": "git@github.com:Beking0912/react-typescript-rollup-starter.git",
"main": "lib/bundle.cjs.js",
"module": "lib/bundle.esm.js",
"browser": "lib/bundle.browser.js",
"types": "types/index.d.ts",
"scripts": {
"build": "rollup -c",
"build:types": "tsc",
},
"author": "Gloria Su",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^11.0.0",
"@types/react": "^18.0.28",
"@types/styled-components": "^5.1.26",
"react": "^18.2.0",
"rollup": "^3.15.0",
"styled-components": "^5.3.6",
"typescript": "^4.9.5"
},
"dependencies": {
"tslib": "^2.5.0"
}
}

Run npm run build and npm run build:types to view the bundled results.

1
2
npm run build
npm run build:types

Add ts-jest

ts-jest simplifies the process of writing and running tests for TypeScript code, and can also make it easier for other developers to contribute to the project.

1
2
npm i jest ts-jest @types/jest -D
npx ts-jest config:init

Add testing scripts to the package.json file.

1
2
3
4
5
{
"scripts": {
"test": "jest",
}
}

Create a test directory in the root folder to store test files, and run npm run test to view the status of the test cases.

Husky configuration

Use Husky to standardize the format of commit messages, which makes it easier to generate a CHANGELOG through scripts later.
Firstly, add the prepare scripts to the package.json file:

1
2
3
4
5
6
7
8
{
"hooks": {
"pre-commit": "npm test && npm run foo"
},
"scripts": {
"prepare": "husky install"
},
}

or

1
2
npm set-script prepare "husky install"
npm run prepare

Configure the Husky hooks in the package.json file using the husky property.

1
npx husky add .husky/pre-commit npm test && npm run foo

Github Action

GitHub Actions allows to automate your entire development workflow, from code changes to production deployment, all within the GitHub environment. The following configuration is mainly to let Github Action help us run lint and test, and when we push a tag, it will help us publish the package to npm and deploy the latest documentation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
name: dry

on:
push:
tags:
- '*' # Push events to every tag not containing /
pull_request:
branches:
- main
workflow_dispatch: # https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/

jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo

- uses: actions/setup-node@v1
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: check code
run: |
npm install
npm run lint
npm run test

- run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
- name: publish
if: ${{contains(github.ref, 'refs/tags/')}}
run: |
npm run build
npm run build:types
npm run docs:build
npm publish --access public
- name: deploy
if: ${{contains(github.ref, 'refs/tags/')}}
uses: JamesIves/github-pages-deploy-action@4.1.1
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs/.vuepress/dist # The folder the action should deploy.

Publish

Update the version number of the package, and push any new tags created to the remote repository.

1
2
3
npm version patch/tag
git push
git push origin --tags

Retrieve the registry URL that is configured for the local npm installation.

1
npm config get registry

Log in to an npm registry with credentials which is necessary in order to publish packages to the registry.

1
npm adduser

Check whether the currently named package already exists.

1
npm info @bk/sum@version

Publish or delete a package.

1
2
3
4
npm publish
npm unpublish @bk/sum --force

npm unpublish @bk/sum@version

How to use

1
npm i bk-sum
1
2
3
import { Sum } from '@bk/sum'

<Sum a={2} b={3}/>

References:

🔍 Check out my code in github.
📮 If find any errors, please feel free to discuss and correct them: biqingsue@[google email].com.