Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save phatnguyenuit/c23f9b4278af5333e78182203d63ee91 to your computer and use it in GitHub Desktop.

Select an option

Save phatnguyenuit/c23f9b4278af5333e78182203d63ee91 to your computer and use it in GitHub Desktop.
How to build a React library with TypeScript

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!

React component

Table of contents

Prerequisites

UPDATES:

Practices

1. Create new project with package.json file

{
  "name": "learn-to-build-react-package",
  "private": true,
  "description": "Learn to to build react package",
  "keywords": [],
  "author": "PhatNguyen <phatnt.uit@gmail.com> (https://phatnguyenuit.github.io)",
  "workspaces": [
    "examples/*",
    "packages/*"
  ],
  "devDependencies": {
    "@types/react": "^17.0.6",
    "@types/react-dom": "^17.0.5",
    "lerna": "^4.0.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}

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

Go back ⏪

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)

Go back ⏪

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

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

Go back ⏪

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 projects 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"
  }

Go back ⏪

5. Configure TypeScript for my-react-package

Our 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"]
}

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 type definitions

declare module '*.module.css' {
    const classes: { readonly [key: string]: string };
    export default classes;
}

declare module '*.module.scss' {
    const classes: { readonly [key: string]: string };
    export default classes;
}

These above types support for importing css modules, scss modules.

Go back ⏪

6. Configure Rollup to bundle our package

  • Install devDependencies

    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

    import typescript from 'rollup-plugin-typescript2';
    import { nodeResolve } from '@rollup/plugin-node-resolve';
    import commonjs from '@rollup/plugin-commonjs';
    import postCSS from 'rollup-plugin-postcss';
    
    import pkg from './package.json';
    
    export default {
      input: 'src/index.ts',
      output: [
        {
          file: './lib/cjs/index.js',
          format: 'cjs',
        },
        {
          file: './lib/esm/index.js',
          format: 'es',
        },
      ],
      external: [...Object.keys(pkg.peerDependencies || {})],
      plugins: [
        nodeResolve(),
        commonjs(),
        typescript({
          typescript: require('typescript'),
        }),
        postCSS({
          plugins: [require('autoprefixer')],
        }),
      ],
    };
  • 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:

Go back ⏪

7. Declare module definition in the packages/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

Go back ⏪

8. Write code for our package

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

    import React from 'react';
    // Import css modules
    import cssClasses from './styles.module.css';
    // Import scss modules
    import scssClasses from './styles.module.scss';
    
    export interface HelloProps {
      name: string;
    }
    
    const Hello: React.FC<HelloProps> = ({ name }) => (
      <div className={scssClasses.helloScss}>
        <p className={cssClasses.helloCss}>Hello, {name}</p>
      </div>
    );
    
    export default Hello;
  • my-react-packages/src/hello/styles.module.css

    .helloCss {
      margin: 0;
      padding: 0;
      color: red;
      font-weight: bold;
      font-size: 2rem;
    }
  • my-react-packages/src/hello/styles.module.scss

    $with: 600px;
    $height: 200px;
    
    .helloScss {
      display: flex;
      align-items: center;
      justify-content: center;
    
      width: $with;
      height: $height;
    
      border: 1px solid red;
      border-radius: 8px;
    }
  • my-react-packages/src/index.ts

    export { default as Hello } from './hello'; // export the default export from './hello', this is Hello component
    export * from './hello'; // export all named exports from './hello' like HelloProps

Go back ⏪

9. Bundle

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

    {
      "scripts": {
        "prepack": "yarn build",
        "build": "rollup -c",
        "watch": "rollup -cw"
      }
    }
    • 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:

    {
      "scripts": {
        "build": "lerna run build --scope my-react-package",
        "watch": "lerna run watch --scope my-react-package",
        "package": "lerna exec --scope my-react-package -- npm pack"
      }
    }
    • build Run build my-react-package
    • watch Watch & build changes my-react-package. This is helpful command in development process to build library if any changes occur.
    • package Pack my-react-package into a package file

Go back ⏪

Usages

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

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

    import { Hello } from 'my-react-package';
    
    import './App.css';
    
    function App() {
      return (
        <div className="App">
          <Hello name="Fast" />
        </div>
      );
    }
    
    export default App;

In the development phase, you can write code for my-react-package and example-app parallelly 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 new bundle will be built automatically.

Go back ⏪

Conclusion

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

  1. Create 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.

Here is my full example code on GitHub repositories. Reaching to it if you want to explore more.

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

Thank you and see you next time!

Go back ⏪

References

Go back ⏪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment