I created my first TypeScript library and published it on NPM
Tags: typescript, npm
Published: 2020-06-15 15:02:40

Recently, I've published my very first library as an NPM module and I'd like to share my learnings and struggles with you.

The module I'm talking about is [ella-math](https://npmjs.com/package/ella-math/), which is a library for vector and matrix calculations. 

These are useful for transformations in graphics programming, like scaling, moving and rotating shapes, or projecting things into 3d space.

## Why?

To be honest, there really is no need to build my own vector/matrix calculation library. There are many of these already out there. But I wanted to do it anyway, just in order to learn how all this works.

## Getting started

The language I've chosen to work with is TypeScript. I initialized the project from scratch:

```sh
mkdir ella-math
cd ella-math
git init
npm init
npm i typescript -D
./node_modules/.bin/tsc --init
```

In order to continue, I had to learn about the basics of the module systems used in JavaScript and by node.js and NPM.

## Module systems

In the first place, NPM (and node.js) uses a module format that is called CommonJS. CommonJS syntax looks like this:

```js
// a vector class
class Vec { /* ... */ }

// a matrix class
class Mat { /* ... */ }

module.exports = {
  Vec,
  Mat
};
```

In node.js, you can use the module by using `require()`:

```js
const { Vec } = require('ella-math');

const a = new Vec(1, 2, 3);
const b = new Vec(4, 5, 6);
const c = a.add(b); // (5, 7, 9)
```

Unfortunately, if you want to try to use it directly in the browser by embedding the library via `<script>` tag, that does not work, because the browser doesn't know about `module` or `require`.

One early approach to get a module system into the browser without having additional build steps was [RequireJS](https://requirejs.com). RequireJS also provides a `require()` function, but as the scripts are loaded asynchronously, the CommonJS syntax could not be used. So, requireJS came up with a module definition format that looks different and wasn't comaptible with CommonJS. It is called Asynchronous Module Definition (AMD). RequireJS isn't pretty common anymore and you will only find it in legacy projects. One selling point for RequireJS was that it even runs on IE6.

So, there were 2 different module formats out there. And sometimes, you don't want to use a module system at all and just use the library via a `<script>` tag.

A fix for this is called Universial Module Definition (UMD). The UMD format provides the best user experience because it supports
CommonJS, AMD and also an export via global variable when no module system is available.

The catch is, an UMD module looks quite messy:

```js
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = global || self, factory(global.Ella = {}));
}(this, (function (exports) {

    exports.Mat = Mat;
    exports.Vec = Vec;

    Object.defineProperty(exports, '__esModule', { value: true });

})));
```

Because this is a mess, the ECMAScript specification came up with another module system: ES modules (ESM). 

It introduced the `import` and `export` keywords into the JavaScript language, which looks a lot cleaner:

```js
export class Vec { /* ... */}
export class Mat { /* ... */}
```
On the consumer side:
```js
import { Vec, Mat } from './ella-math.esm.js';
```
```html
<script type="module" src="index.esm.js"></script>
```

Because this specification introduced a keywords, older browsers cannot understand this flavor of JavaScript.

To address this, there is a `type="module"` attribute that tells the browser the JavaScript is using the modern module system. Old browsers don't execute it at all (like IE11). 

So, in an ideal world, you want to just use the modern module specification. But NPM uses CommonJS in the first place. Additionally, you may want to provide a way to directly use your library in the browser.

So, I ended up still using the UMD module format but also providing the modern ES module format.

## Compiling to UMD and ESM

To compile my library to an UMD module, I am using [Rollup](https://rollupjs.org) alongside with the official TypeScript plugin. Rollup both supports the UMD format and the ES module specification out of the box, so I can provide both flavors.

The rollup configuration (rollup.config.js) looks like this:

```js
import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/ella.ts',
  output: [
    {
      file: 'dist/ella.esm.js',
      format: 'es',
    },
    {
      file: 'dist/ella.umd.js',
      format: 'umd',
      name: 'Ella',
    },
  ],
  plugins: [typescript()],
};
```

My scripts section in the `package.json` looks like this:

```json
{
  "scripts": {
    "test": "jest",
    "docs": "typedoc && touch docs/.nojekyll",
    "build:types": "tsc -t esnext --moduleResolution node -d --emitDeclarationOnly --outFile dist/ella.d.ts src/ella.ts",
    "build:js": "rollup -c rollup.config.js",
    "build:minjs": "terser dist/ella.umd.js --compress --mangle > dist/ella.umd.min.js",
    "build": "npm run build:js -s && npm run build:minjs -s && npm run build:types -s"
  }
}
```

Because the official rollup typescript plugin does not work with emitting type definitions, I'm using an additional build step to generate a `.d.ts` file containing all type definitions for TypeScript. 

Currently, the `declaration` and `sourceMap` options in tsconfig throw an error when using `@rollup/plugin-typescript`.

There is a rewrite of the typescript plugin [rollup-plugin-typescript2](https://www.npmjs.com/package/rollup-plugin-typescript2) that fixes the issue with type definitions and source maps, but I haven't tried it.

To also provide a minified bundle, I'm using [terser](https://npmjs.com/package/terser/) which works quite well together with Rollup.

Additionally, I'm using [typedoc](https://npmjs.com/package/typedoc/) to generate [API documentation](https://terabaud.github.io/ella-math/) from code comments using [JSDoc notation](https://jsdoc.app).

## Specify files for publishing

To specify which files are going to be published, you specify all the things inside your `package.json`. 

The entry file goes in the `main` field, the type definition goes into the `types` field. Finally, you define a `files` field with an array containing the files and folders to be included:

```js
{ 
  /* ... */ 
  "main": "dist/ella.umd.js",
  "types": "dist/ella.d.ts",
  "files": ["src", "dist"]
  /* ... */
}
```

## Publishing on NPM

After you have created an account on NPM, you can log in to your account inside your command line shell:

```sh
npm login
```

Then, choose a package name and an initial version in your `package.json`:

```js
{
  "name": "ella-math",
  "version": "1.0.0",
  /* ... */
}
```

You will have to chose a package name that is unique. To finally 
release your package on NPM, do

```sh
npm publish
```

## Counting versions up

After you have made your changes, make sure everything is committed and you are on the main branch. `npm` provides a built in versioning count tool:

```sh
npm version major  # v1.0.0 -> v2.0.0
npm version minor  # v1.0.0 -> v1.1.0
npm version patch  # v1.0.0 -> v1.0.1

# publish the new version
npm publish
```

Use major for breaking changes (every time your API changes).
The version command also creates a git tag for the specific version inside your repo. To push it to your remote repo, use:

```sh
git push
git push --tags
```

## The released project

So, I must say, publishing a library on NPM that was created in TypeScript is not super straight forward. 

You can check out the whole project at github: 

https://github.com/terabaud/ella-math/

A demo using this library is on CodePen:

https://codepen.io/terabaud/pen/MWazXyd