Plugins are a great way to share code between projects. At Reckonsys we have multiple projects where we need to sh are 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.
Before proceeding with this tutorial, you should have a basic understanding of the following:
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.
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.
Let’s add Rollup and the other plugins that we need to use to generate the plugin
npm i -D rollup rollup-plugin-typescript2 @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-postcss node-sass sassview raw, gistfile1.txt hosted with ❤ by GitHub
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.
"main": "build/index.js",
"module": "build/index.es.js",
"files": ["build"],
plugins: Let’s go through the list of plugins that we have used
Here is a list of all the available plugins in Rollup.
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
import React from "react";import { SampleComponentProps } from "./SampleComponent.types";import "./SampleComponent.scss";const SampleComponent: React.FC<SampleComponentProps> = ({ text }) => (<divclassName="sample-component"><h3>This is a Sample component</h3><div data-testid="sample-text" className="sample-text">{text}</div></div>);export default SampleComponent;view raw , rollup2 hosted with ❤ by GitHub
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“.
import SampleComponent from './SampleComponent/SampleComponent';export { SampleComponent };view raw , rollup3 hosted with ❤ by GitHub
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.
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
npm i --save-dev jest ts-jest @types/jest identity-obj-proxy @testing-library/react @testing-library/jest-dom jest-environment-jsdomview raw, rollup4 hosted with ❤ by GitHub
Then paste the following code into “jest.config.ts“.
export default {clearMocks: true,collectCoverage: true,coverageDirectory: "coverage",moduleNameMapper: {"\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|
wav|mp3|m4a|aac|oga)$":"identity-obj-proxy","\\.(css|less|scss|sass)$": "identity-obj-proxy"},setupFilesAfterEnv: ["./jest.setup.ts"],testEnvironment: "jest-environment-jsdom",testMatch: ["**/*.spec.(ts|tsx)"],
transform: {"^.+\\.tsx?$": "ts-jest"},};view rawrollup5 hosted with ❤ by GitHub
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
import React from "react";import { render } from "@testing-library/react";import SampleComponent from "./SampleComponent";import { SampleComponentProps } from "./SampleComponent.types";
describe("Sample Component", () => {let props: SampleComponentProps;beforeEach(() => {props = {text: "sample"};});it("should have text as sample", () => {const { getByTestId } = render(<SampleComponent {...props} />)const testComponent = getByTestId("sample-text");expect(testComponent).toHaveTextContent("sample");});});view raw, rollup6 hosted with ❤ by GitHub
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:
> testing_react_plugin@1.0.0 test> jestPASS src/SampleComponent/SampleComponent.spec.tsxSample Component✓ should have text as sample (17 ms)---------------------|---------|----------|---------|---------|-------------------File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s---------------------|---------|----------|---------|---------|-------------------All files | 100 | 100 | 100 | 100 |SampleComponent.tsx | 100 | 100 | 100 | 100 |---------------------|---------|----------|---------|---------|-------------------Test Suites: 1 passed, 1 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 1.312 s, estimated 3 sRan all test suites.view rawrollup7 hosted with ❤ by GitHub
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“
{"name": "testing_react_plugin","version": "1.0.0","description": "","main": "build/index.js","module": "build/index.es.js","files": ["build"],"scripts": {"test": "jest","test:watch": "jest --watch","prepublishOnly": "npm run build","build": "rollup -c","watch": "rollup -c -w"},"author": "","license": "ISC","peerDependencies": {"react": ">=16.8.0","react-dom": ">=16.8.0"},"devDependencies": {"@rollup/plugin-commonjs": "^24.0.0","@rollup/plugin-node-resolve": "^15.0.1","@testing-library/jest-dom": "^5.16.5","@testing-library/react": "^13.4.0","@types/jest": "^29.2.4","@types/react": "^18.0.26","identity-obj-proxy": "^3.0.0","jest": "^29.3.1","jest-environment-jsdom": "^29.3.1","node-sass": "^8.0.0","react": "^18.2.0","react-dom": "^18.2.0","rollup": "^3.7.5","rollup-plugin-peer-deps-external": "^2.2.4","rollup-plugin-postcss": "^4.0.2","rollup-plugin-typescript2": "^0.34.1","sass": "^1.57.1","ts-jest": "^29.0.3","ts-node": "^10.9.1","typescript": "^4.9.4"}}view raw, rollup8 hosted with ❤ by GitHub
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;
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
Run the plugin from the projects root directory using the following command
Then open another terminal. Enter the example app and run the example app alongside the plugin app.
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.
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.
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.
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;
Let's collaborate to turn your business challenges into AI-powered success stories.
Get Started