Creating a React plugin with Rollup, & Typescript

Plugins are a great way to share code between projects. At Reckonsys we have multiple projects where we need to share components between two or more applications. We create plugins so that we can share components in an easy way. This helped us to speed up the development process. In this tutorial, we will learn how to create a plugin for React and how to publish it to npm.

We will be using Rollup for bundling the plugin, and we will be using Typescript. It is common for plugins to be written in typescript, and the developers who use your plugin will appreciate you for the type definitions. There are many Javascript bundlers available to create a plugin, webpack is a popular option. Rollup caught my interest after we started using Vite for our React Apps. I learned that Vite uses Rollup under the hood.

Prerequisites

Before proceeding with this tutorial, you should have a basic understanding of the following:

  1. ReactJS
  2. RollupJS
  3. TypeScript
  4. React Testing Library

1. Setting up the Project

Let’s start by creating an empty javascript project. Run the following in your terminal.

npm init

This will ask you for details regarding the project. You can give “testing_react_plugin” as the name. We will come back later to fill in the other details, as of now you can leave them blank. Let’s initialize git for our project. Run “git init” at the root directory of the project. Next, we need to install react and react-dom.

npm i --save-dev react react-dom @types/react

We need to configure react and react-dom as peer dependencies because when we use this plugin in another project, it won’t add react and react-dom as dependencies, but it will show a warning message saying that a matching version hasn’t been installed along with our plugin. Also, it won’t bundle React in the build files. Add the following to your package.json

"peerDependencies": {
  "react": ">=16.8.0",
  "react-dom": ">=16.8.0"
}

We need to use Hooks in our React app, so we are targeting a minimum react version >=16.8.0.

2. Adding Typescript

Run the following to install typescript as a devDependency

npm i -D typescript ts-node

create a file called tsconfig.json and add the following

{
    "compilerOptions": {
        "declaration": true,
        "declarationDir": "build",
        "module": "esnext",
        "target": "es5",
        "lib": [
            "DOM",
            "ES2020"
        ],
        "sourceMap": true,
        "jsx": "react",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "build",
        "src/**/*.spec.tsx"
    ]
}

The critical thing to note here is declaration and declarationDir. This is where we specify our output directory where our components and types will be placed after they are generated.

3. Adding Rollup

Let’s add Rollup and the other plugins that we need to use to generate the plugin

create a file called “rollup.config.mjs” and add the following:

import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import postcss from "rollup-plugin-postcss";

export default {
    input: "src/index.ts",
    output: [
        {
            file: "build/index.js",
            format: "cjs",
            sourcemap: true
        },
        {
            file: "build/index.es.js",
            format: "esm",
            sourcemap: true
        }
    ],
    plugins: [
        peerDepsExternal(),
        resolve(),
        commonjs(),
        typescript({ useTsconfigDeclarationDir: true }),
        postcss()
    ]
};

Let’s go through the config file

input: This is the entry point to the project. We will be importing/exporting our components from src/index.ts file. We will add this file shortly.

output: We have supplied an array with two objects. We need to generate our components for two types of Javascript module formats. CommonJS — CJS and ES Modules — ESM. You can read more about it here.

Our plugin needs to work on projects that use CommonJS and ES Modules.

ES Modules have some features like tree shaking, which CommonJS does not support.

We also need to specify the path to the CommonJS and ES Module index files in the “package.json“. The main field in package.json points to our bundled CommonJS entry point, and the module field points to our bundled ES Modules entry point. If the project that uses this plugin uses ES modules it will use “module” otherwise, it will use “main“. Update your package.json by adding the following

"main": "build/index.js",
"module": "build/index.es.js",
"files": ["build"],

plugins:

Let’s go through the list of plugins that we have used

  • rollup-plugin-peer-deps-external: This plugin will prevent peer dependencies that are mentioned in package.json (react and react-dom) from being bundled along with the build files
  • @rollup/plugin-node-resolve: Is used to bundle 3rd party dependencies that are used in our project
  • @rollup/plugin-commonjs: Is used to convert our code to CommonJS format
  • rollup-plugin-typescript2: Is used to transpile our typescript code to javascript. It will use the configurations that are mentioned in tsconfig.json. We have set useTsconfigDeclarationDir as true so that the .d.ts files will be put inside the build folder that we mentioned in tsconfig.json 
  • rollup-plugin-postcss: We will be using Sass in the plugin so we need this plugin to convert sass files to CSS.

Here is a list of all the available plugins in Rollup.

4. Adding a component to the plugin:

Let’s add a component to our plugin by adding the following files and directories.

jest.config.ts
jest.setup.ts
src/
  SampleComponent/
    SampleComponent.tsx
    SampleComponent.types.ts
    SampleComponent.scss
    SampleComponent.spec.tsx
  index.ts

Add the following code into “SampleComponent.tsx” file

Add the following to the “SampleComponent.types.ts” file

export interface SampleComponentProps {
    text: string;
}

Open “src/index.ts” file and import and export the “SampleComponent“.

When we start adding more components to this project, we will use this file to import and export the components. This will be useful as our users will have a single file from which they can import all our components.

5. Adding React testing library and Jest:

There is no better way to maintain our code other than to write test cases. This will ensure that we are not making any changes that will break the application. We will be using React Testing Library and Jest.

Run the following command to install the necessary packages for our application

Then paste the following code into “jest.config.ts“.

Add the following to jest.setup.ts file. We will be using jest-environment-jsdom as our testing environment. After jest version 28, if we want to use jest for client-side applications, we need to explicitly add a testing environment.

import "@testing-library/jest-dom";

This will add a lot of useful helper functions like “toBeDisabled“, and “toHaveAttribute“. Add the following script to your “package.json” file.

"test": "jest",
"test:watch": "jest --watch",

The "--watch” flag will continuously watch for file changes.

Add the following to your “SampleComponent.spec.tsx” file

In case you are unfamiliar with Unit testing, we have passed the text “sample” as props to the component and then we are checking if the same text is being rendered in the component. If you run the test case with “npm run test” it should show you the following output:

6. Building the project:

Add the following to the script section in your package.json to build the plugin.

"prepublishOnly": "npm run build",
"build": "rollup -c",
"watch": "rollup -c -w"

Now if you run the “build” command, it should generate the build files under the “build” folder.

The following is the final configuration in our “package.json

7. Adding a React application to run the plugin:

Now we can build and test our plugin, but we still need to see what our plugin looks like in the browser. Let’s add a react application that will use our plugin within our plugin project. Run the following command at the root directory of the project:

npx create-react-app example

After the react app is installed, enter into the example directory (cd example) and add the plugin to the example app using the following command:

npm i --save ../

If you check the package.json file in the example app, you will find the following under the dependencies section

"testing_react_plugin": "file:..",

Clear everything in example/src/App.js file and import our plugin as shown below:

import { SampleComponent } from 'testing_react_plugin';

function App() {
  return (
    <div>
      <SampleComponent text="sample" />
    </div>
  );
}

export default App;

You should see the following output in your browser

Cheers, now we have successfully used our component in the example app.

The way how you can develop and test the components in the plugin is

  1. Run the plugin from the projects root directory using the following command
    npm run watch
  2. Then open another terminal. Enter the example app and run the example app alongside the plugin app.
    npm start

Now, if you make any changes in the components of the plugin project, it will instantly reflect in the browser.

I tried using storybookjs along with this setup, but I had difficulty setting it up with the latest node version.

8. Publishing the plugin

Make sure that you have built the project. The contents inside the “build” directory will be published; we have specified this in the “files” section in the “package.json

To publish the app, you need to create an account in npm. We will concentrate on npm in this tutorial, but there are other ways to achieve the same. Then login into npm in your terminal using the following command

npm login

Our plugin’s name is testing_react_plugin which is already taken, so you need to use another unique name. You can publish your plugin.

npm publish

When you run the above command, the “prepublishOnly” command in our “package.json” will automatically run which will prepare the build.

If you want to update your plugin, you will have to increment the version in “package.json” following the Semantic Versioning guide. For example, if I had a “patch” update, I’d change the version from 1.0.0 to 1.0.1 and run “npm publish” again.

9. Using the plugin

Assuming that we have pushed our plugin to npm at: https://www.npmjs.com/package/testing_react_plugin, we can install the plugin as a dependency similar to any other package. In your React app run the following.

npm install --save testing_react_plugin

You can then import that component into your app as shown below

import { SampleComponent } from 'testing_react_plugin';

function App() {
  return (
    <div>
      <SampleComponent text="sample" />
    </div>
  );
}

export default App;

You can refer to the above source code in this GitHub repo.

79

You may also like

Leave a Reply

Your email address will not be published. Required fields are marked *