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
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
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
To configure Babel with the two installed presets, create a .babelrc
with the below content:
{
"presets": [
"@babel/preset-react",
"@babel/preset-env"
]
}
Create a folder for your code
📂my-components
┃ …
+┣ 📂src
+┃ ┗ 📜index.js
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>
}
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"
},
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
- "transpile": "babel src -d lib --copy-files",
+ "transpile": "rimraf lib && babel src -d lib --copy-files",
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"
+ ],
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"
+ },
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
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",
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
To use the package you can just install it like any other package in your React project:
npm install @receter/my-components
And import the button like this:
import { Button } from '@receter/my-components'
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:
- Run
npm link
in the folder of the package you want to link to. This will register your package locally. - 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
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",
…
}
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)