DEV Community

Cover image for Creating and testing a react package with CRA and rollup
Nwakwoke Patrick Nnaemeka
Nwakwoke Patrick Nnaemeka

Posted on • Updated on

Creating and testing a react package with CRA and rollup

TL;DR

check out the complete setup in this repo

Introduction

This article assumes that you have at least a beginner level knowledge of react. It is not intended to teach you react.

You may create a nice component in your app that you see a use case for in other apps. You could also have an idea for a component that you would like to make available to other developers. The problem usually is, how do you bundle this component and make it distributable. In this article, I will provide guidelines on how we can easily bundle our component with rollup, test it locally and make it available for installation on npm.

Setting up a project

We will be making use of create react app(CRA) to setup react in this guide. We can either have CRA installed globally on our machine or we can use it directly via npx which ensures we are running the latest version. Running the following command sets up our project with the name 'react-dog':

npx create-react-app react-dog
Enter fullscreen mode Exit fullscreen mode

Project structure

We will create a folder named lib under the src folder. This is where all the source code for the component or library will be. In the lib folder we will have the following files:

  • the styles (dog-style.css)
.dog {
  display: flex;
  max-width: 100px;
}
Enter fullscreen mode Exit fullscreen mode
  • an image (dog-image.jpg)
    Alt Text

  • the main component file (Dog.js)

import React from 'react';
import dog from './dog-image.jpeg';
import './dog-style.css';

export default function Dog() {
  return (
    <div class='dog'>
      <img alt='dog' src={dog} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Our folder structure will end up looking like this:

Folder structure

First Test Run

To ensure that there are no errors and our package is working like it is supposed to before bundling, you should import your component or Dog in our case into App.js and run this project. Replace the content of App.js with this:

import React from 'react';
import Dog from './lib/Dog.js';

function App() {
  return (
    <div className='app'>
      <Dog />
    </div>
  );
}

export default App
Enter fullscreen mode Exit fullscreen mode

Run the project with npm start and everything should run smoothly before you proceed to the next step

Setting up rollup and babel

Run this command to install rollup and all the plugins we require to bundle our package.

npm i -D rollup rollup-plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-image rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-visualizer
Enter fullscreen mode Exit fullscreen mode

Here is a brief description of what each plugin does:

Run this to install babel and the babel plugins needed for your compilation

npm i -D @babel/cli @babel/core @babel/preset-env @babel/preset-react
Enter fullscreen mode Exit fullscreen mode

Configurations

Create a babel configuration file .babelrc in the root of your project and insert the following content:

{
  "presets": ["@babel/env", "@babel/preset-react"]
}
Enter fullscreen mode Exit fullscreen mode

These presets contains plugins that babel will utilize when converting the library from ES6 and JSX to a lower javascript versions.

Next, we need to configure package.json by adding fields that will point it to the entry point of your package after bundling. We will be adding both a main and a module field. The main field makes sure that Node users using require will be served the CJS version. The module field is not an official npm feature but it will enable ES6 aware tools to make use of an ES6 version of your library. So, we are generating two builds. Modify your package.json by adding the following fields:

{
...
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
}
Enter fullscreen mode Exit fullscreen mode

The values of main and module above indicate that the bundles will be generated in a dist folder. You can also add a files field to specify what must be published to npm. Files like LICENSE, CHANGELOG.md and README.md are published by default.

...
"files": [ "dist/*" ]
Enter fullscreen mode Exit fullscreen mode

The above setting specifies that all files in the dist folder must be published to npm.

Move react and react-dom from the dependencies field to devDependencies. Specify the minimum version of react and reactDom that your package requires in the peerDependencies field. I am using >=16.8.0 in this example because it is the earliest version with support for hooks. At this point, your dependencies field should be empty or deleted while your peerDepedencies and devDependencies fields should look similar to this.

{
...
  "peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.8.7",
    "@babel/preset-env": "^7.8.7",
    "@babel/preset-react": "^7.8.3",
    "@rollup/plugin-commonjs": "^11.0.2",
    "@rollup/plugin-image": "^2.0.4",
    "@rollup/plugin-node-resolve": "^7.1.1",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "react-scripts": "3.4.0",
    "rollup": "^2.0.6",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-peer-deps-external": "^2.2.2",
    "rollup-plugin-postcss": "^2.4.1",
    "rollup-plugin-visualizer": "^3.3.1"
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

Next, create a file named rollup.config.js in the project's root directory. This is where all the configurations for rollup will be specified. Insert the following content into rollup.config.js:

import babel from 'rollup-plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import resolve from '@rollup/plugin-node-resolve';
import image from '@rollup/plugin-image'
import visualizer from 'rollup-plugin-visualizer';
import pkg from './package.json';

export default {
  input: './src/lib/Dog.js',
  output: [
    {
      file: pkg.main,
      format: 'cjs'
    },
    {
      file: pkg.module,
      format: 'esm'
    }
  ],
  plugins: [
    external(),
    postcss(),
    babel({
      exclude: 'node_modules/**'
    }),
    resolve(),
    commonjs(),
    image(),
    visualizer()
  ]
};

Enter fullscreen mode Exit fullscreen mode

This is what each configuration field stands for:

  • input: The entry point to the component you want to bundle. In this article, I am pointing directly to Dog.js but it is common to find projects where a main file like index.js file is created to export the component(s)

  • output: This specifies the directory where you want to save the bundled library. With rollup, you can specify an array of multiple outputs in different formats and directories. We are importing the output paths from package.json

  • plugins: This specifies all the plugins you wish to use and their respective configurations. You can look up documentation on each plugin if there is a need to configure them differently.

Bundling

After setting up and configuring our tools, the next step will be to create a bundle that will be distributed through npm or any other package manager of your choice.

Running rollup -c compiles the component into a bundle using the configurations in rollup.config.js. We will modify our build script in package.json so that we can execute npm run build whenever we want to generate a new bundle.

...
build: "rollup -c"
Enter fullscreen mode Exit fullscreen mode

Run npm run build and you should have a dist folder with two bundled files in it. If you added the rollup-plugin-visualizer, you will also get a stats.html file that you can open in your browser to inspect the bundle size.

Testing the bundle

Next, we should make sure our bundle works before publishing it on npm. One easy way to do this locally is to link the package to our global node_modules. This can be done by running npm link on the command line from the root of the project. Ensure you have specified the name of your package in package.json because that will be your package's name.

name: "react-dog"
Enter fullscreen mode Exit fullscreen mode

After running npm link, a symlink will be created for the dist folder in the global node_modules.

Next, run npm link react-dog(replace 'react-dog' with your package name) from the root of the project. This will create another symlink between your global node_modules and your current local node_modules for react-dog so that you can simply import it in App.js like your other installed third-party libraries.

import Dog from 'react-dog'
Enter fullscreen mode Exit fullscreen mode

From the script above, you can observe that we no longer import Dog from './lib/Dog.js'. Run npm start and we should see that the library is working as it should. Rebuild if you make any changes so that you can test the updated version of your library.

Publishing

Now that everything is in place, we can prepare our package for publishing. Similar to .gitignore, it is advisable to create a .npmignore file that will contain all the files irrelevant to our publication. This helps cut down on the package size.

src
rollup.*
.babelrc
.eslintrc
stats.html
Enter fullscreen mode Exit fullscreen mode

I have excluded the src folder from the library because we are only using it for tests.

You can read more on including and excluding files here.

If you aren't already logged in to npm, run npm login on the command line and follow the prompts to fill out your login details.

Finally, run npm publish and if everything goes well, your package will be available for installation from anywhere by simply running npm install <package-name>

Troubleshooting

Some common problems you might encounter during this process include:

  • is not exported by module: This mainly occurs with some packages in node_modules. While can be any named imports, you will have to specify it manually in the commonjs plugin added to your configuration in rollup.config.js. Example, for the following error: Alt Text

a fix will be to add it to the namedExports field in the commonjs plugin like this:

plugins: [
...
commonjs({
  namedExports: {
        'node_modules/react-is/index.js': ['isElement', 'isValidElementType']
      }
})
]
Enter fullscreen mode Exit fullscreen mode

Read more on this error here

  • React scripts requires a dependency(webpack):

Alt Text

If you get the error above when trying to run the project with npm start(react-scripts), install a webpack version that is compatible with your version of react-scripts as a devDependency. Don't worry about installing a wrong version, react-scripts will raise an error that informs you of the correct version.

npm run i -D webpack@version
Enter fullscreen mode Exit fullscreen mode
  • Cannot find module after linking package: After linking your package, if your application refuses to build or start when trying to build or test your bundle due to any missing module error, follow these steps to fix the issue:
  1. Delete node_modules
  2. Run npm install
  3. Run npm link <package>

Try restarting or rebuilding the project.

Conclusion

Rollup provides a very neat way of bundling javascript. It keeps the bundle relatively readable in case a user of your package needs to debug it in node_modules.

If you need access to the actual files for this setup, you can check the repo

Top comments (9)

Collapse
 
thesymbeint profile image
Dandre

This was a great post very informative.

Collapse
 
bdacoscos profile image
Briana Dacoscos • Edited

@emeka Small typo! The description of package "@rollup/plugin-node" should be "@rollup/plugin-node-resolve" :)

Collapse
 
emeka profile image
Nwakwoke Patrick Nnaemeka

Thank you Briana, I have corrected it

Collapse
 
bdacoscos profile image
Briana Dacoscos

And thanks for the article! I especially appreciated the breakdown of each of the packages and a summary of what they do :)

Collapse
 
fabiradi profile image
Fabian Rademacher

Small typo installing the rollup packages:

# wrong
npm i -D rollup @rollup-plugin-babel ...
# corrected (without "@")
npm i -D rollup rollup-plugin-babel ...
Collapse
 
emeka profile image
Nwakwoke Patrick Nnaemeka • Edited

Thanks for this Fabian, it could be @rollup/plugin-babel or rollup-plugin-babel

Collapse
 
seanmclem profile image
Seanmclem

you don't define pkg in package.json

Collapse
 
emeka profile image
Nwakwoke Patrick Nnaemeka

I can import it with any name I choose, pkg represents package.json, not a property in package.json

Collapse
 
wrongnerfs profile image
Hello

Curious why you are not using: preserveModules: true, won't that reduce ur bundle size?