DEV Community

Cover image for The minimal setup to package and reuse your React components
Andreas Riedmüller
Andreas Riedmüller

Posted on • Edited on

The minimal setup to package and reuse your React components

Creating an npm package for sharing React components may seem overwhelming at first. At least that's how I felt and it took me some time to learn how to do it. One thing that particularly confused me was that everyone seemed to do it a little differently.

This article contains what I believe is the bare minimum to create an npm package with React components.

Init your package

npm init
Enter fullscreen mode Exit fullscreen mode

First, create a new folder (eg. my-components) and run npm init to bootstrap a package.json file.

Install the dependencies needed for transpiling

npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react
Enter fullscreen mode Exit fullscreen mode

To publish React components as an npm package you need to transpile your javascript to ES5. As far as I know, there is no way around that, even if you only want to use it in ES6 projects.

You will need @babel/core for transpilation, @babel/cli to run the babel command, and the configuration presets @babel/preset-env (for modern Javascript) and @babel/preset-react (for jsx).

Configure Babel

 📂my-components
  ┃ …
 +┃ 📜.babelrc
Enter fullscreen mode Exit fullscreen mode

To configure Babel with the two installed presets, create a .babelrc with the below content:

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

Create a folder for your code

 📂my-components
  ┃ …
 +┣ 📂src
 +┃ ┗ 📜index.js
Enter fullscreen mode Exit fullscreen mode

It’s good practice to create a src folder for all your source code. The index.js file will be the main entry point of your package (actually not this one but the transpiled version of it). If you don’t have something else in mind, you can export this simple button component, for example. Just copy the code to src/index.js:

import React from 'react';

export function Button(props) {
  const {
    children,
    ...elementProps
  } = props;
  return <button type="button" {...elementProps}>{children}</button>
}
Enter fullscreen mode Exit fullscreen mode

Setup scripts and the main entry point in your package.json

-  "main": "index.js",
+  "main": "lib/index.js",
   "scripts": {
     
+    "transpile": "babel src -d lib --copy-files",
+    "prepublishOnly": "npm run transpile"
   },
Enter fullscreen mode Exit fullscreen mode

To transpile your code you will have to call babel on the src folder and specify an output directory using either --out-dir or just -d. The last argument --copy-files will also copy files that are not compiled, like images for example.

This will transpile the entire src directory and save the results to the output directory. A good place to store your transpiled files is a folder named lib.

Babel will overwrite but not delete any existing files or directories in the output directory. To be sure the lib folder doesn’t contain old files you can delete it before transpiling. To do this automatically you can install rimraf and add it to the transpile script like this:

npm install --save-dev rimraf
Enter fullscreen mode Exit fullscreen mode
-    "transpile": "babel src -d lib --copy-files",
+    "transpile": "rimraf lib && babel src -d lib --copy-files",
Enter fullscreen mode Exit fullscreen mode

You can now npm run transpile and take a look at the transpiled code in lib.

The second script’s name (prepublishOnly) is a magic one. It will automatically run before your package gets published. This will make sure you don’t forget transpiling when you publish your package.

Finally set the main entry point of the package to the transpiled index file lib/index.js

Included files and peer dependencies

Before the package is ready to be published you might want to define what’s inside and what isn’t. You can exclude files with a .npmignore file or create a whitelist with the files property in package.json. In this case the whitelist is definitely the better option:

+  "files": [
+    "lib",
+    "readme.md"
+  ],
Enter fullscreen mode Exit fullscreen mode

With this whitelist only the lib folder (and package.json) will be shipped in the final package. I also included readme.md because it is quite common to have one. Feel free to create this file in the root of your project if you want.

By adding peerDependencies to your package.json you can require that certain host packages need to be installed when using your package. If the peer dependencies are not met a warning is shown to the user.

+  "peerDependencies": {
+    "react": ">=17.0.2",
+    "react-dom": ">=17.0.2",
+    "prop-types": ">=15.7.2"
+  },
Enter fullscreen mode Exit fullscreen mode

This will require that at least react 17.0.2, react-dom 17.0.2 and prop-types 15.7.2 must be installed to use your package.

Ok, that’s it, your package is ready for publishing.

Publish/install your package

Basically there are two options to consume your package. You can publish and install it from a NPM registry or link it locally with npm link.

Publish your package to a NPM registry

You can publish your package with the command npm publish.

Per default your package will get published to npmjs.com and everyone can install it. If you want your package to stay private, you need a paid plan. Or you can install and host your own registry with something like Verdaccio.

As you can’t publish a package with the same version twice, you need to bump the version in your package.json before publishing an update of your package.

npm version <newversion> is a handy command for that and it also updates package-lock.json and npm-shrinkwrap.json. You can call it with a full new version number like 1.0.1 or bump either the major, minor or patch part of the version. See the npm version and semantic versioning to learn more.

To update your package from 1.0.0 to 1.0.1 just run these two commands:

npm version patch
npm publish
Enter fullscreen mode Exit fullscreen mode

I have published the package from this example to my user scope on npm.

To do this I had to prefix the name of the package with my username:

-  "name": "my-components",
+  "name": "@receter/my-components",
Enter fullscreen mode Exit fullscreen mode

And when I first published it, I had to confirm that I really want my package to be public by using --access public. Per default all scoped packages on npm are private.

npm publish --access public
Enter fullscreen mode Exit fullscreen mode

To use the package you can just install it like any other package in your React project:

npm install @receter/my-components
Enter fullscreen mode Exit fullscreen mode

And import the button like this:

import { Button } from '@receter/my-components'
Enter fullscreen mode Exit fullscreen mode

Using npm link

npm link is especially useful for testing/developing your package. Npm will create a symlink to your package in node_modules and any changes you make to the linked package are available immediately.

To link your package you have to follow these two steps:

  1. Run npm link in the folder of the package you want to link to. This will register your package locally.
  2. In the project where you want to use the package run npm link <name-of-the-package>

If you use nvm or another node version management tool: Be sure to use the same node version for npm link in the package folder as you do in your project.

To remove the link run npm unlink <name-of-the-package>

Important: If you have already installed a version of <name-of-the-package>, npm link … will replace the installed package with the symlink. This is probably what you want, but note that npm unlink will not only remove the symlink but also the dependency in your package.json.

So in this case you need to explicitly tell npm unlink not to save the changes and run npm install afterwards to install the original package again.

npm unlink --no-save <name-of-the-package>
npm install
Enter fullscreen mode Exit fullscreen mode

Another thing to note is that you have to transpile your files before you can actually see changes made in src. You can automate this by calling babel with the --watch option. Just add another script like this:

   "scripts": {
+      "watch": "babel src -d lib --copy-files --watch",
       
   }
Enter fullscreen mode Exit fullscreen mode

Now you can run npm run watch in your package directory and see all changes you make to your components live in your project!

Ok, that’s it for now. If you have any questions feel free to ask me.

You can find the code for my example on github:
https://github.com/receter/my-components
https://www.npmjs.com/package/@receter/my-components

How do you share components between your projects? What do you do differently and why? Or do you use a service like BIT? Please let me know!

Top comments (0)