October 11th 2021

Developing NPM Packages using Yarn and Typescript

This article is written to document my process of starting, developing, and deploying a package to the node package manager (npm) repositories. Most if not all of the content in here will be subjective and pertain to my own experience.

Final Result

In this article I will be describing the experience of writing the scyllo, an opinionated cassandra/scylla standard library, on a 6 hour and 19 minute train drive.

Inspiration

To start things off I found this article by Carl-Johan Kihl, and decided to walk through its steps.

Why typescript?

I am a firm believer that everything should have types. Javascript itself is incomplete without typescript. Although typescript may not provide runtime type-safety it does enhance the developer experience and therefore is a win in my book. Regardless of your opinion, im using typescript.

NPM vs Yarn

NPM and Yarn are both package manager that make use of the NPM ecosystem. Personally I am biased towards yarn as it is what works most smoothly to me, not to forget to mention that I have aliased the yarn command to y and (later for pure comedy why). Which results in the best combinations ever, y start, y watch, y live, y build, y init, y create, y start, you get the gist.

Setup

To get started we must setup a git repository and yarn package

git init
yarn init
git add .
git commit -m "Initial Commit"

Typescript

Next we will be setting up typescript, first lets add it as a dev dependency

yarn add -D typescript

The -D flag is being used here to let npm know that the typescript package used here is only used here and will not be needed once our library has been imported into the target project.

Next we will be creating a tsconfig.json file in the root of our project. In my instance this looks as follows

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "declaration": true,
        "outDir": "./lib",
        "strict": true
    },
    "include": ["src"],
    "exclude": ["node_modules", "**/__tests__/*"]
}

Basic code setup

For this library I have decided to place all of the code inside of a folder called src. This is purely preferential as you could give this folder any name of your choosing, lib, files, code, etc.

Inside of lib/index.ts we will put something simple.

export const Greeting = () => {console.log('Hello Scyllo')};

Once we have this exported lets head into our package.json file and setup a build script. The final package.json file should look something like this.

{
  "name": "scyllo",
  "version": "0.0.1",
  "description": "The Cassandra/Scylla library you didn't want but got anyways.",
  "main": "index.js",
  "repository": "[email protected]:lvkdotsh/scyllo.git",
  "author": "Lucemans <[email protected]>",
  "license": "MIT",
  "devDependencies": {
    "typescript": "^4.4.3"
  },
  "scripts": {
    "build": "tsc"
  }
}

Building

Now that we have done the basic setup of our project it is time to compile the now typescript files back down to javascript + type-definitions. To do this we simply run

yarn build

A new folder called lib (see tsconfig to configure name) should have appeared. In this folder we will find the compiled javascript versions of our code aswell as the type-definitions that one could use to utalize our library.

Formatting and Linting

Now that our code is compiled, and built, it is time to add linting to the project. Do this in whatever way you so please.

Intelligent Run Scripts

Furthermore there are some npm default package scripts that might come in handy. These scripts are executed upon the occurance of particular events and can help you automate the lifecycle of your development and deployment.

The prepare script will run BEFORE the package is packed and published, as well as on an install on your local machine (in development). An example of something you might want to put in your prepare script would be the following

"prepare": "yarn build"

The prepublishOnly script will run BEFORE prepare and only on npm publish. This lets us do things like linting, and testing, as follows.

"prepublishOnly": "yarn test && yarn lint"

The preversion script will run BEFORE we bump our library to a new version. To be extra sure, linting can be done here aswell.

"preversion": "yarn lint"

The version script will run after a new version has been bumped. If your package has a git repository, like in our case, a commit and a new version-tag will be made every time you bump a new version. This command will run BEFORE the commit is made. One idea is to run the formatter here and so no ugly code will pass into the new version:

"version": "yarn format && git add -A src"

The postversion script will run after the bumped commit has been made. This is the perfect place to also push new tags or update anything else, or other occurances of the version number. If you would like to combine the git commit hash with the version number this would be the time to do it.

"postversion" : "git push && git push --tags"

Finally our scripts section in our package.json should look something like this:

"scripts": {
  "test": "jest --config jestconfig.json",
  "build": "tsc",
  "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
  "lint": "tslint -p tsconfig.json",
  "prepare": "yarn build",
  "prepublishOnly": "yarn test && yarn lint",
  "preversion": "yarn lint",
  "version": "yarn format && git add -A src",
  "postversion": "git push && git push --tags"
}

Final State

As I am currently on the train and writing this, the package.json looks like this.

{
  "name": "scyllo",
  "version": "0.0.1",
  "description": "The Cassandra/Scylla library you didn't want but got anyways.",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "repository": "[email protected]:lvkdotsh/scyllo.git",
  "author": "Lucemans <[email protected]>",
  "keywords": ["cassandra", "scylla", "scyllo", "database", "client"],
  "license": "MIT",
  "devDependencies": {
    "typescript": "^4.4.3"
  },
  "scripts": {
    "test": "echo yes",
    "build": "tsc",
    "lint": "tslint -p tsconfig.json",
    "prepare": "yarn build",
    "prepublishOnly": "yarn test && yarn lint",
    "preversion": "yarn lint",
    "version": "git add -A src",
    "postversion": "git push && git push --tags"
  }
}

As you can most likely tell I have added keywords the main, types, and removed testing because as of writing I have not written those :shrug:.

Publishing

Now we are finally ready to publish our package. To publish it is as simple as running

yarn publish

Updating Version Number

To perform later updates on your package you must increase the version number. Although you could do this manually, we can actually have yarn do it for using following SemVer by running one of the following

yarn version --patch
yarn version --minor
yarn version --major
yarn version --prepatch
yarn version --preminor
yarn version --premajor
yarn version --prerelease

And then following it up with

yarn publish

~Luc