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
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;
}
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>
);
}
Our folder structure will end up looking like this:
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
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
Here is a brief description of what each plugin does:
rollup-plugin-babel: This integrates rollup with babel.
@rollup/plugin-commonjs: Converts any commonjs module to ES6.
@rollup/plugin-node-resolve: Locates third party modules in
node_modules
@rollup/plugin-image:Imports your images and svg icons.
rollup-plugin-peer-deps-external: Externalize dependencies in a rollup bundle. This is automatic for peerDependencies.
rollup-plugin-postcss: Transforms styles with js plugins. You need this if your package contains styles
rollup-plugin-visualizer: Visualize and analyze your Rollup bundle to see which modules are taking up space.
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
Configurations
Create a babel configuration file .babelrc
in the root of your project and insert the following content:
{
"presets": ["@babel/env", "@babel/preset-react"]
}
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",
}
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/*" ]
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"
}
...
}
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()
]
};
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 likeindex.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"
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"
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'
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
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 inrollup.config.js
. Example, for the following error:
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']
}
})
]
Read more on this error here
- React scripts requires a dependency(webpack):
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
- 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:
- Delete node_modules
- Run
npm install
- 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)
This was a great post very informative.
@emeka Small typo! The description of package "@rollup/plugin-node" should be "@rollup/plugin-node-resolve" :)
Thank you Briana, I have corrected it
And thanks for the article! I especially appreciated the breakdown of each of the packages and a summary of what they do :)
Small typo installing the rollup packages:
Thanks for this Fabian, it could be @rollup/plugin-babel or rollup-plugin-babel
you don't define pkg in package.json
I can import it with any name I choose, pkg represents package.json, not a property in package.json
Curious why you are not using: preserveModules: true, won't that reduce ur bundle size?