How to build a React library with TypeScript

Today, I am going to show you how to build a React library with TypeScript. Let’s get started!

How to build a React library with TypeScript

Prerequisites

  • [lerna](https://lerna.js.org/) - A tool for managing JavaScript projects with multiple packages.
  • [Yarn workspace](https://classic.yarnpkg.com/lang/en/docs/workspaces/) - Setup NodeJS workspace
  • [React Components](https://reactjs.org/docs/components-and-props.html) - Basic knowledge about React Components
  • Understand NodeJS module system [ECMAScript modules - ESM](https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules) and [CommonJS - cjs](https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules).

UPDATES:

2022–01–29: Add CSS/SCSS modules supports when building React library with TypeScript and Rollup. Check the updates on section 5 (Configure TypeScript for my-react-package) and section 8 (Write code for our package).

Practices

1. Create a new project with a package.json file

Root workspace package.json file

Two important points

  • private should be turned to true
  • workspaces contains workspace paths. I use packages/* to provide that my packages should be implemented under the packages folder, and examples/* for all examples with my built libraries

Folder structure

./learn-to-build-react-package  
 |  |-- package.json  
 |  |-- examples  
 |  |   |-- example-app  
 |  |   |   |-- package.json  
 |  |-- packages  
 |  |   |-- my-react-package  
 |  |   |   |-- package.json

2. Configure lerna

{  
  "npmClient": "yarn",  
  "useWorkspaces": true,  
  "version": "independent"  
}

Notes:

  • npmClient: whether npm or yarn client called when running lerna command
  • useWorkspaces use workspace flag
  • version: we should choose independent to make every single package in our workspace has an independent version, not the same)

3. Create new package packages/my-react-package

mkdir packages/my-react-package;  
cd packages/my-react-package;  
npm init -y;

4. Install peerDependencies for packages/my-react-package

npx lerna add react --scope my-react-package --peer;  
npx lerna add react-dom --scope my-react-package --peer;

Why do we use peerDependencies?

=> It is because our package scope is just a MODULE that can be installed by any project and which must have our package peerDependencies installed also.

Now our my-react-package peerDependencies section in the package.json file looks like below

"peerDependencies": {  
    "react": "^17.0.2",  
    "react-dom": "^17.0.2"  
  }

5. Configure TypeScript for my-react-package

Create my-react-package/tsconfig.json file should look like below:


{
  "compilerOptions": {
    "outDir": "lib/esm",
    "module": "esnext",
    "target": "es5",
    "lib": ["es6", "dom", "es2016", "es2017"],
    "jsx": "react-jsx",
    "declaration": true,
    "moduleResolution": "node",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "esModuleInterop": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "allowSyntheticDefaultImports": true
  },
  "include": ["src/**/*.ts*"],
  "exclude": ["node_modules", "lib"]
}

my-react-package/tsconfig.json file

Some highlights:

  • outDir stands for output directory after compiled TypeScript to the target ECMAScript version es5
  • Must use "jsx": "react-jsx" to use JSX compiler
  • Turn on declaration to extract type definitions
  • Folder “node_modules” and “lib” must be excluded while compiling TypeScript to JavaScript.

Create new my-react-package/src/global.d.ts to define global types

my-react-package/src/global.d.ts file

6. Configure Rollup to bundle our package

  • Install devDependencies

cd packages/my-react-package;
yarn add -D rollup typescript rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-postcss postcss node-sass

  • Create new file my-react-package/rollup.config.js

my-react-package/rollup.config.js file

Our module exposes two types of module system: CommonJS — cjs and ECMAScript — esm

All packages in the peerDependencies section will be treated as external dependencies. It means Rollup does not include them in the bundling process.

We use some Rollup plugins:

7. Declare module definition in the my-react-package/package.json file

{  
  "main": "./lib/cjs/index.js",  
  "module": "./lib/esm/index.js",  
  "types": "./lib/esm/index.d.ts",  
  "files": \[  
    "/lib"  
  \],  
}
  • Here we define the main file for our project is "./lib/cjs/index.js"
  • Our package also expose esm module at "./lib/esm/index.js"
  • Type definitions will be included at "./lib/esm/index.d.ts"
  • The last important is "files", which tells NPM which files or folders will be packaged. For our package, it will be "lib" folder

8. Write code for our package

  • my-react-package/src/hello/index.tsx

my-react-package/src/hello/index.tsx file

  • my-react-package/src/hello/styles.module.css

my-react-package/src/hello/styles.module.css file

  • my-react-package/src/hello/styles.module.scss

my-react-package/src/hello/styles.module.scss file

  • my-react-package/src/index.ts

my-react-package/src/index.ts file

9. Bundle

  • In the my-react-package/package.json file add some useful commands:

my-react-package scripts

prepack Run before packing library into a package file. Eg: packages/my-react-package/my-react-package-1.0.0.tgz

build Build source code

watch Watch & build changes

  • In the root workspace package.json file add some useful commands:

workspace scripts

build Run build my-react-package

watch Watch & build changes my-react-package. This is a helpful command in the development process to build a library if any changes occur.

package Pack my-react-package into a package file

Usages

  • Create a new React App (prefer to TypeScript template) under ./examples folders:

cd examples;
create-react-app example-app --template typescript;

  • Install package my-react-package into example-app:

npx lerna add my-react-package --scope example-app

I use lerna add command here to get my-react-package installed into example-app and have the latest source code of my-react-package if any new bundles

Now just import and see how it works on examples/example-app/src/App.tsx:

Example App.tsx file

In the development phase, you can write code for my-react-package and the example-appparallelly by using:

  • Start example-app:

cd examples/example-app;
yarn start;

  • Watch and build our package my-react-package if any changes

yarn watch

So now, when you want to develop new components or hooks or anything else, you just write the code and our my-react-package will be built automatically.

Conclusion

To sum it up, there are 9 steps to build a React Library with TypeScript:

  1. Create a new project with package.json file
  2. Configure lerna
  3. Create new package packages/my-react-package
  4. Install peerDependencies for packages/my-react-package
  5. Configure TypeScript for my-react-package
  6. Configure Rollup to bundle our package
  7. Declare module definition in the package.json file
  8. Write code for our package
  9. Bundle

Last but not least, thank you for reading through this section! I hope you find this article helpful and solve your concerns when trying to build a React library.

If you have any questions or feedbacks, do not hesitate to leave a comment in the box below.

Thank you and see you next time!

References

  • [Lerna](https://lerna.js.org/)
  • [Yarn workspace](https://classic.yarnpkg.com/lang/en/docs/workspaces/)
  • [React Components](https://reactjs.org/docs/components-and-props.html)
  • [NodeJS peerDependencies](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#peerdependencies)
  • [ECMAScript modules -](https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules) ESM
  • [CommonJS - CJS](https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules)
  • [EcmaScript Exports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export)