Publishing a JavaScript Library

Published on

Publishing a JavaScript library is a great way to share your code with the world. But for newcomers, it can be a bit confusing. This article will help you to achieve this goal easily.

Hello guys, and happy new year! I hope you are doing well.

Lately, i've been working on a javascript library i made called react-plock, a simple react masonry implementation. And i decided to publish it on npm, and make it available for everyone.

So, being my first time publishing a library (in a correct way.. πŸ˜…) I searched for a good way to do it on the web essay, but the problem I encountered is that there are a LOT of different ways to do it, some people use webpack, some others use rollup, some actually does not use nothing.. and others again use Bit, everyone telling that theirs is the "definitive way to do it", and for me (and probably for most of us), it was a bit confusing to find a sort of 'standardized' way to do it.

So, after some research i found a very good free course by @kentcdodds on egghead.io and i decided to invest my time and follow it. This was one of the best decisions i made, and i'm very happy with the result i achieved, but the course it's a little bit old (written about 7 years ago πŸ“…), so i decided to update the publishing part writing step by step what i've done to publish my first javascript library on npm.

The topics that will be covered in this article are:

  • How a library is read by your project.
  • How to publishing a JavaScript library.

There will be also an article about how to manage the versioning of your library. I will attach it to this article, when it will be ready.

How a library is read by your project

The first thing that you need to understand before publishing a library, is actually how it's read by the project that uses it. It is a very simple, but important concept that you must know. So let's start with an example. Let's pretend that we have a project called "my-project" that uses the library "react-plock" we just published πŸ™‹. In order to use it, we need to import it in our project.

import { Plock } from "react-plock"; // Look at me πŸ‘€!

export default function App() {
  return (
    <div className="container">
      <Plock />
    </div>
  );
}

The question is: how the fu*k is the library "react-plock" read by the project "my-project"?

Let's try to answer this question looking it from another perspective. Imagine you have in your amazing "my-project" a custom component made by you called Banana, and you wanna use it in the App component. How can you accomplish this?

import { Banana } from "./components/Banana"; // Look at me πŸ‘€!

export default function App() {
  return (
    <div className="container">
      <Banana />
    </div>
  );
}

Notice that the only thing that's changed from the previous example is the path to the component source code, that in this case, is a relative path, not a library name.

In turn, the Banana could be structured like this:

// Look at the export statement πŸ‘€!
export function Banana() {
  return (
    <div className="bomb">
      <h1>I am a Banana</h1>
    </div>
  );
}

So, the App component imports the Banana component, by reading an exported function called Banana from the ./components/Banana.js file.

Note: There is also the default export available as option, but i personally prefer the named export when i work with modules.

Well, but, how about the react-plock library?

By now you should have understood that in order to import a module, you need to specify two fundamental things. The first one is the path to the module, and the second one is the name of the module.

To import an external library, our beloved package.json will come to our aid. It's a file that contains all the information about a library, and every library has its own package.json.

If you have a look at the (package.json)[https://github.com/itsrennyman/react-plock/blob/main/package.json#L6] file of the react-plock library, you'll see some intersting information that i will highlight for you:

{
  "name": "react-plock",
  "main": "dist/index.js"
}

Did you notice the main property? It's the path to the library source code. This is the file that will be imported when the library will be used!

Sooo, returning to our question, how the library "react-plock" is read by the project "my-project"? I attach again the initial example to avoid you scrolling again to the top of the page:

import { Plock } from "react-plock"; // Look at me πŸ‘€!

export default function App() {
  return (
    <div className="container">
      <Plock />
    </div>
  );
}

Guessed? Well, once the App.js file is included, and it evaluates the Plock import, node will look for the package.json file of the react-plock library (included in the node_modules folder of your project during the installation), once it finds it, it will read the main property and will import the library source code!

Note: The main property can be any file generated in your library, but the most common way to do it is to use the dist folder.

How to publishing a JavaScript library

So, now that we have the basic knowledge importing a library works, let's go to the next topic: how to publish a JavaScript library.

We will create an amazing library called gimme-a-pokemon, and we will publish it on npm. The package will provide a function that will return a random pokemon name from an array (The package of the year 🌻).

Let's start by creating the index.js file of the library. This will be our main file in the package.json, and it will contain the function that will return the pokemon name. We will use the commonjs syntax to export the function in order to be compatible with older versions of node and not use any transpiler in the process. In a second step we will add the transpiled version of the library to the dist folder.

module.exports = {
  gimmeAPokemon: () => {
    const pokemon = [
      "Pichu",
      "Pikachu",
      "Raichu",
      "Sandshrew",
      "Sandslash",
      "Nidorina",
      "Nidoking",
      "Clefairy",
      "Clefable",
      "Vulpix",
    ];

    return pokemon[Math.floor(Math.random() * pokemon.length)];
  },
};

The code is pretty simple, call the gimmeAPokemon function and return a random pokemon name.

We will skip testing the library for now, due to the fact that it's very simple.

Now it's time to create the package.json file. This file will contain all the information about the library, and it will be used by npm to publish it. You can run the following command to create the package.json file:

$ npm init

The npm init command will create a package.json file in the current directory, and it will ask you some questions about the library.

After the command is executed, you will have a package.json file in your current directory, here's a quick description of some of the properties in the file:

PropertyValue
nameThe name of the library
descriptionA short description of the library
versionThe current version of the library
privateIf the library is private, it will not be available for the public
mainThe path to the library's main file
filesThe files that will be published on the registry
authorThe author of the library
licenseThe license of the library

Note: There are some keys like main and files which are not present by default. Also, there are other keys that you can use, but there are out of the scope of this article.

This is the gimme-a-pkmn real library example, i omitted for this example the useless keys like scripts, devDependencies, keywords, etc.. you can look at the entire file here

{
  "name": "gimme-a-pkmn",
  "private": false,
  "version": "0.0.1",
  "description": "A function to get a random pokemon name",
  "main": "index.js",
  "files": ["index.js"],
  "author": "Renato <renato.pozzi.dev@gmail.com> (https://renatopozzi.me/)",
  "license": "MIT"
}

Once you have configured the file, all we need to do is to login in your npm account, and publish the library.

$ npm login
$ npm publish

And that's it! The library is published on npm with the version 0.0.1 and you will be able to install it in your project using npm install gimme-a-pkmn.

Using a transpiler to write using ES6 features

Yeah our library works great, but we can't use the ES6 features in our code. Or better, we could, but not every environment supports ES6, so we could have a problem. Let's see how to solve this problem using a transpiler.

Unpopular opinion: Transpilers are simple. They are just a tool to convert ES6+ features to ES5.

I told you this because if you look at almost every tutorial on the internet you will see tons of line of webpack, rollup configurations without any explanation of what they actually do. So it may scare you, but trust me it's not a big deal. I will explain you like i would explain a child.

The concept is simple, as i said just before, transpilers are just a tool to convert ES6+ features to ES5, so they takes an input file and output a transpiled file. This is currently all you need to know about transpilers. Simple right?

Look at this example, this is a modern arrow function:

const arrowFunction = () => {
  console.log("This will be transpiled!");
};

If you try to run this code in an old browser, you will see that it will not work. In order to fix this, we need to use a transpiler. Let's see how the code becomes after the transpilation:

var arrowFunction = function arrowFunction() {
  console.log("This will be transpiled!");
};

Did you see the difference? The transpiler will convert the arrow function to a regular function, and it will add the function keyword and the var keyword.

You can so some experiments with this configuration here.

Obviously there's a lot more to know about transpilers, but for now, let's focus on the basics.

Integrating a transpiler with a bundler

Another scary name you should know is bundler. Bundlers are tools that take a bunch of files and compile them into a single file. Usually are useful because they have also a lot of plugins, so you can easily integrate a transpiler like babel we just saw, with a bundler. I personally find rollup the bundler which i'm most comfortable with. So we will use rollup with its babel plugin to compile our code, don't worry, it's not a big deal.

So, our goal now is: we want to use rollup to bundle our code, and we want to use babel to transpile our code. Let's see how to do it.

First, we need to install the rollup and babel plugin packages as dev dependencies.

$ npm install --save-dev rollup @rollup/plugin-babel @babel/preset-env

Once the packages are installed, we need to create a rollup.config.js file. This file will contain all the configuration for your bundling process.

import { babel } from "@rollup/plugin-babel";

// We will use the main configuration property directly from the package.json file
const packageJson = require("./package.json");

/* eslint-disable */
export default {
  input: "index.js", // The path to the file that will be bundled
  output: [
    {
      file: packageJson.main, // The path to the bundled file
      format: "cjs", // The format of the bundled file (commonjs in this case)
    },
  ],
  plugins: [
    // The plugin that will be used to transpile our code, in this case we will use babel
    babel({ babelHelpers: "bundled", presets: ["@babel/preset-env"] }),
  ],
};

Please take some time to read again the code, it's pretty simple, but it's important to know how it works. Remember, also in bundling there is an input, and there is an output. And the process between is called bundling.

Ok now we need to add a command to run the bundler. Again we will use the package.json file, we can insert a new script called "build":

"scripts": {
  "build": "rollup -c",
  // ...other scripts
},

And the last thing we need to do is to change the main property of the package.json to another destination, otherwhise our index.js file will be overwritten. Also the files property will be updated as well because we need to publish the dist directory.

{
  "main": "dist/index.js" // Here again our lovely dist folder! πŸ’–
  "files": [
    "dist"
  ],
}

Now we are ready to run the bundler. Go ahead and run the command npm run build. You should see the bundled file in the just created dist folder. Check the differences between the two files! πŸ₯Έ

Now your code can be used in any modern browser, and it will work just fine. You can also rewrite it using modern ES6 features:

export const gimmeAPokemon = () => {
  const pokemon = [
    "Pichu",
    "Pikachu",
    "Raichu",
    "Sandshrew",
    "Sandslash",
    "Nidorina",
    "Nidoking",
    "Clefairy",
    "Clefable",
    "Vulpix",
  ];

  return pokemon[Math.floor(Math.random() * pokemon.length)];
};

By running the command npm run build again, you should see the bundled file with the new code, fully compatible with modern and old browsers.

You can also publish your new code to npm, just run the command npm publish. But remember to bump the version number in the package.json file, otherwise the new version won't be published.

You can find the source code used for this tutorial on GitHub, also you can find the package on npm.

Common Problems

There are some common problems that you might encounter when publishing a JavaScript library. Be sure to check the following:

  • The library name must be unique, and it must be a valid npm package name.
  • You cannot publish a library version that is already published, so you need to bump the version number before publishing.

Conclusion

Phew! That was a lot of work, but you have learned a lot about JavaScript libraries. Now you can use them in your projects and you can publish them on npm. You can even use them in your own projects! πŸŽ‰ πŸŽ‰ πŸŽ‰

Soon i will publish also a detailed guide on how to manage the version of your libraries, totally automated, using Github actions. So stay tuned!

If you have any questions, feel free to contact me on Twitter.

Cheers! 🍻

Get Access to Exclusive Content!

I'm going to produce some great stuff exclusively for front-end developers. I'll post new content including tips, tutorials and early access to future resources. Just subscribe and you’ll get my best posts delivered right to your inbox. I promise you won’t regret.