Nowadays most of the web apps we develop are component-based, and to take full advantage of components we reuse them in not only the same application but in different applications as well. One of the common use cases is when an organization has multiple applications and you, as a developer, need a view in one application which you already developed in some other application. Of course you won’t copy-paste the code. Okay, you will. But what if you find some issue in one app and edit your component? You’ll have to do the same change in all other applications wherever you copied the code, right? Yep, and it doesn’t make sense to do it, so…. let’s just publish your component in a registry and reuse.
Reuse: I know we all have reused our code at one point or many in our lifetimes, but the point is, we are talking about using it in different apps.
Versioning: Whatever changes you do in one place can be implemented at the others with ease. All you need to do is publish it to a registry for reuse. One of the popular examples of a registry is “npm”.
and more of course….
Initiate: Create a package.json file by running npm init
in whichever directory you want to.In my case, I named my folder as react-publishable-component
. The script will ask for a few details, enter those and you’ll get something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
“
name”: “react - publishable - component”,
“version”: “1.0 .0”,
“description”: “”,
“main”: “index.js”,
“dependencies”: {},
“devDependencies”: {},
“scripts”: {
“
test”: “echo\” Error: no test specified\” && exit 1 "
}
}
<optional>
: Whenever we install any package npm creates a package-lock.json
which I don’t like creating, so, let’s tell npm not to do that.
Create a file named .npmrc
and content as package-lock=false
.
Why do I not like the package-lock file?
It is fine having a package-lock file as long as you are working alone on the project. Problems arise when you are working with a team and someone includes the package-lock.json in the version control system (GIT) with the code.
Package-lock includes the complete version and dependency<->sub-dependency tree with all specific versions mentioned. It helps npm find and download the previously installed version of dependencies the next time you install modules. However, when there is a version change for one of the sub-dependencies of any package your code depends on, many times npm ends up downloading conflicting/old dependencies for newer packages as per the old dependency tree in the package-lock. That breaks the application as every developer’s system normally has a different package-lock dependency tree.
The above step is optional, if you don’t want to do it, that’s fine.
Dependencies: We’ll need to install a few dependencies using the command below
1
npm install @babel / cli @babel / core @babel / plugin - proposal - class - properties @babel / preset - env @babel / preset - react babel - loader css - loader prop - types react style - loader url - loader webpack webpack - cli webpack - node - externals--save - dev
Above are the dependencies which we mostly end up using when we have components with a lot of things with them, like css, etc. In our case, we don’t need all these. Instead, the few below are sufficient:
1
npm install @babel / core @babel / plugin - proposal - class - properties @babel / preset - env @babel / preset - react babel - loader webpack webpack - cli webpack - node - externals--save - dev
Babel: Basically, we use babel to compile our jsx files or ES6 code to lower the version of javascript which can be run by most of the browsers.
Webpack: Is a package bundler which helps us in bundling our numerous dependencies into a single file or clubs those to a few.
For example: we have twenty .js files which we have marked as dependencies in our index.html in a project. Webpack’s dependency graph helps us by making a static bundle out of those twenty so we don’t end up calling some dependency prior to another one which it is dependent on….I know, it’s a mess, we’ll talk about it in another story.
Let’s set up our package.json:
Out of dev dependencies, move react to peerDependencies’ array because we require react to get our package working: <you can move others as well, like
prop-types` or some other dependency>
1
2
3
4
5
“
peerDependencies”: {
“
react”: “16.13 .1”
},
Babel config file: for compiling your react code to an older version of javascript, create filename .babelrc and add below lines to it:
1 2 3
“ presets”: [“@babel / preset - env”, “@babel / preset - react”], “plugins”: [“@babel / plugin - proposal - class - properties”] }
Webpack config file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const path = require('path'); // <-get absolute location for saving
const pkg = require('./package.json');
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: "./src/index.js", // <- starting point for bundle
output: {
path: path.resolve(__dirname, 'dist'), //<-where to save ur bundle
filename: "index.js", //<-filename for bundled file
library: pkg.name,
libraryTarget: "commonjs2" //<- to which version are we compiling js
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
}
]
},
target: 'node',
Externals: [nodeExternals()]
// if we need to bundle anything extra with our code, if nothing entered means nothing else to compile, else we can mention, if, in case we want to bundle a particular third party code with our codebase
// if we need to bundle anything extra with our code (3rd party package),we can add it as an argument to this method.
// else, if nothing is passed as argument means no additional dependency to compile.
};
edit your package.json:
1 2 3 4 5 6 7 8 9 10 11
"main": “dist / index.js”, //<-the bundled file we mentioned in webpack config "scripts": { "start": “webpack— watch”, "build": “webpack”, "prepublish": “npm run build” //<- so I do not have to run two commands to build and publish, thanks to node's 'pre' hook }, "repository": { "type": “git”, "url": https: //github.com/psharneja/react-publishable-component }, "author": “Prabhsimran Singh < psharneja @gmail.com > (http: //psharneja.github.io/)", “license”: “ISC”,
So with adding most of the information, our package.json will look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
"name": "react-publishable-component", "version": "1.0.9", // <- need to update version every time you publish your component "description": "", "main": "dist/index.js", "scripts": { "start": "webpack — watch", "build": "webpack", "prepublish": "npm build" }, "repository": { "type": "git", "url": "https://github.com/psharneja/react-publishable-component" }, "author": "Prabhsimran Singh <simarharneja@googlemail.com> (http://github.com/psharneja)", "license": "ISC", "devDependencies": { "@babel/cli": "7.7.7", "@babel/core": "7.7.7", "@babel/plugin-proposal-class-properties": "7.7.4", "@babel/preset-env": "7.7.7", "@babel/preset-react": "7.7.4", "babel-loader": "8.0.6", "css-loader": "3.4.1", "prop-types": "15.7.2", "react": "16.12.0", "style-loader": "1.1.2", "url-loader": "3.0.0", "webpack": "4.41.5", "webpack-cli": "3.3.10", "webpack-node-externals": "7.7.2" }, "peerDependencies": { "react": "16.13.1" } }
To the folks who got confused with npm prepublish
running npm build
command, the reason I did this is:
Npm ‘pre’ and ‘post’ hooks help us run multiple commands in one series if we want to. Here, in my case, I wanted to run npm build
command prior to npm publish, hence, I used pre
publish to run npm build
. If I wanted to run a command after npm publish
I would’ve written it with help of post hook as postpublish
.
Yes - our components. Let’s create a folder named src
and create a file named index.js
Write our component’s code in it:
1
2
3
4
5
6
7
8
9
10
11
12
import React from“ react”;
const RandomButton = props => {
const {
width,
height,
color,
text
} = props;
return (<button style={{ width: width || 100, height: height || 100,
backgroundColor: color || “blue” }} > {text} </button>);
};
export default RandomButton;
Now, when we npm build
command we’ll get a bundle file (index.js) in dist
folder which we can publish to any node package registry (thanks to our webpack config).
Above is how we bundled one component, but what if we want to bundle multiple components in one package?
Let’s move this component code to a separate file, let’s create a file with the same name as the component.
code-image
Here, I created two components and one index file as starting point:
1
2
3
4
5
6
7
8
9
//Code for second component
import React from“ react”;
const RandomText = props => {
const {
texty
} = props;
return (<input value={texty || “something written”} />);
};
export default RandomText;
Webpack requires us to give one entry point, which we have given as index.js
. But in our case we have two files, so let’s create an index file which will export one or two components:
1 2 3 4 5 6 7 8
export { default as RandomButton } from‘. / RandomButton’; export { default as RandomText } from‘. / RandomText’;
That’s all for creating publishable components. Next step, publishing the components:
Publishing is way simpler than you think. Once you have your bundle ready:
i. create an npmjs.org account.
ii. run npm login
to…of course, login.
iii. run command npm publish
which will publish your component with name and version mentioned in your package.json
file.
Not always do we push our code to npm registry, for many organizations have separate registries (nexus mostly) where we need to push our package. For that, all we need is to write one extra line to .npmrc file:registry=<registry url>
By default npm publishes to https://registry.npmjs /
but we can change that by mentioning like we just did, above.
Note: it will be same for yarn, all you need to do is to write the above in .yarnrc
file.
Npm will publish anything you have in the directory which we do not really want, so let’s tell npm what not to publish:
Create a file named .npmignore
and add all the file names and types you don’t want to publish. Example: normally we publish the build and not the source code, mention those files like this:
1 2 3
src .babelrc webpack.config.js
This is extra, and I think most of the people know it, still… sharing for the uninitiated:
All we need to save in our git repo is the source code, and not the build/ bundle. So it’s the exact opposite case than what we did for ignoring source code files whilst publishing.
Create a file named .gitignore
Add the files/ folder type or name you don’t want to push to git:
1 2
dist node_modules
In our case, it’s just one folder named dist.
Install your new npm package. In my case it was npm i react-publishable-component
Import in your component:
1 2 3 4
import { RandomButton, RandomText } from‘ react - publishable - component’;
Use the component needed:
1 2
<RandomButton/> < RandomText texty = ”something written here” / >
Update the entry point of your webpack file accordingly.
Add peer dependencies to your package.json
Set version in package.json file.
You are good to go.
Just like versioning of code in git, npm registries require a similar kind of versioning of your code to track and differentiate between the coding changes you do, and sometimes for people to use the specific version of your code they want to and not always the one with latest changes.
For every version change, we need to update the version value in your code’s package.json file. Npm uses the convention of versioning called semantic versioning or SemVer.
The version is divided into three parts separated by dots. The names given are major, minor, and patch, respectively.
MAJOR.MINOR.PATCH
As the name says, if your code has any major changes which will probably break the code which was using your package as dependencies, you should change the major version of the package.
We normally update the version number for minor when there is a code change but that doesn’t break your code.
Patch is normally used when you have done some fixing on your component. For example, I updated the border-radius of my button from 50vh to 50% as it was not consistently circular in case of responsive apps.
Check the code out at:
https://github.com/psharneja/react-publishable-components