How to Make a Javascript Library - Building With Typescript & Publish
How to Make a Javascript Library - Building With Typescript & Publish

In this post I want to show how you can create your Javascript library using Typescript, transpile it first of all to CommonJS and to ES6 modules and publish on npm registry.

Do we have a problem?

So what is the problem in creating library? Actually publishing a folder with some scripts on npm is super easy. But the main problem is that we have 2 modular systems inside Javascript: we have CommonJS and ES6 modules. Some projects might want to use your library inside NodeJS and in some projects your library will be used in the browser on the client.

Which actually means that the main problem is that we must transpile our library in 2 different versions. And maybe later in 3 or 4 if it is needed. For this task we could use Babel because it can transpile to any possible Javascript version.

But I will use here Typescript. Why Typescript? First of all nowadays we write more and more projects with Typescript. Secondly inside configuration of Typescript we can simply provide in what modular system we want to transpile our Typescript code.

Creating a helper

Here I created an empty folder with src folder inside. This is where all our code will be situated. Let's say that we want to create a library with helpers for any project. For testing we will create just a single helper for arrays which is called pluck. The main idea of this method is to map an array by specific field.

// src/array/pluck.ts
export const pluck = (elements, field) => {
  return elements.map(element => element[field])
}

Here is the expected usage. We can get an array of ids from array of objects.

const ids = pluck([{name: 'Jack', id: '1'}, {name: 'John', id: '2'}], 'id')

Exporting a helper

As we want to make everything clean and reusable we need to create a single entry file with all public functions that we want to expose.

// src/index.ts
export {pluck} from './array/pluck'

Configuration files

Now in our root folder we want to create 2 different tsconfig.json files to transpile in CommonJS and in ES6.

// tsconfig.es5.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES5",
    "declaration": true,
    "outDir": "./dist/lib/es5",
    "moduleResolution": "node",
    "lib": ["ES2020", "DOM"]
  },
  "include": ["src/**/*"]
}
// tsconfig.json
{
  "compilerOptions": {
    "module": "ES6",
    "target": "ES5",
    "lib": ["ES2020", "DOM"],
    "declaration": true,
    "outDir": "./dist/lib/es6",
    "moduleResolution": "node"
  },
  "include": ["src/**/*"]
}

es5.json we will use to transpile to node.js to be used in a server. This is why we set there commonjs as a module. In tsconfig.json we set module to ES6. Other then that our configs are super similar. Also it is important to mention that typescript will build our source code in 2 different folders inside dist. In /dist/lib/es6 and /dist/lib/es5.

Now we must prepare a package.json file in a root folder. It is not to deploy to npm but just to push to github. Then other developers can see the links to the project, license and all needed information.

{
  "name": "monsterlessonsacademy",
  "version": "1.0.0",
  "repository": "git@monsterlessonsacademy.github.com:monsterlessonsacademy/monsterlessonsacademy.git",
  "author": "Oleksandr Kocherhin <monsterlessonsacademy@gmail.com>",
  "license": "MIT",
  "scripts": {
    "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json"
  },
  "devDependencies": {
    "typescript": "^4.6.3"
  }
}

Here we set that we need Typescript for our project and we create compile command. As you can see it removes dist/lib folder and called tsc and tsc --build tsconfig.es5.json. So it will cal Typescript build twice with 2 different configs.

Now we need to install packages and call compile command.

npm install
npm run compile

As you can see in dist/lib 2 new folder with compiled Javascript were created.

Preparing to publish

Now we must create package.json inside dist folder for our library? Why there? Because we will publish just our dist folder and it needs package.json to be correctly published.

// dist/package.json
{
  "name": "@mla/utils",
  "version": "0.0.1",
  "description": "A set of JavaScript utils",
  "main": "lib/es5/index.js",
  "module": "lib/es6/index.js",
  "sideEffects": false,
  "repository": {
    "type": "git",
    "url": "git+https://github.com/monsterlessonsacademy/utils.git"
  },
  "files": [
    "lib",
    "CHANGELOG.md",
    "LICENSE",
    "package.json",
    "README.md"
  ],
  "keywords": [
    "javascript",
    "utils",
    "utilities"
  ],
  "author": "Oleksandr Kocherhin",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/monsterlessonsacademy/utils/issues"
  },
  "homepage": "https://github.com/monsterlessonsacademy/utils#readme"
}

Here we provided all necessary links, keywords and a name of our future library. As you can see here we have a naming @mla/utils. We make this @mla prefix we be able to namespace different projects and libraries with our own custom name because each name must be unique.

Also we have here 2 important lines: main and module. Here we set which file build system must take depending of environment.

Uploading to Github

First of all we must create a .gitignore file in our root folder.

// .gitignore
node_modules
dist/lib

Here we ignored node_modules and lib which is generated. It is important to not ignore dist folder completely as we store our package.json for npm there.

Now we must just to github and create a new repostory. After this we can push our code there.

git init
git add .
git commit -am "Finished library"
git remote add origin PATH_TO_YOUR_REPOSITORY
git push origin master

As you can see our branch is pushed and we can see our code on github.

Publishing to NPM

We must just to dist folder and try to publish our library.

cd dist
npm publish

and as expected we get an error that we must authorize in npm first.

npm adduser

After this command you must enter your username and password from npm and you will get a one time password on your email to finish adding a user.

If you will try to publish once again you probably will get a message

402 - Payment required

It tries to publish your package as private and you get an error. This is why we must tune our publish command.

npm publish --access=public

After this your library is successfully published. We can jump to npmjs.org and find it in search just by name.

Testing

Now it's time to test it. Let's install it just in our project

npm install YOUR_LIBRARY_NAME

Let's create a file to check it inside nodejs.

// server.js
const { pluck } = require("@mla/utils");
console.log(pluck([{ name: "foo" }, { name: "bar" }], "name"));

We can now run this script with node to check that it is working.

node server.js

As you can see our helper works!

Now we need to test it on the client. But it will be a module so we can't just put it in index.html. We need to build it. For this I will use parcel package which allow us to build index.html with all dependencies.

// client.js
import { pluck } from "@mla/utils";
console.log(pluck([{ name: "foo" }, { name: "bar" }], "name"));
<!DOCTYPE html>
<html>
  <head>
    <title>This is the title of the webpage</title>
  </head>
  <body>
    <script src="client.js" type="module"></script>
  </body>
</html>
  "scripts": {
    "compile": "rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json",
    "parcel": "parcel index.html"
  },
  "devDependencies": {
    "parcel": "^2.5.0",
    "typescript": "^4.6.3"
  }
npm run parcel

With this command we get a web server which is running and compiles our changes on the fly. And as you can see it works as expected

So this is how you can publish your library with Typescript. And if you are interested to learn how to upload files to your website make sure to check this post also.

📚 Source code of what we've done