Home Publishing an NPM Package
Post
Cancel

Publishing an NPM Package

My programming language track is from Pacal, C++, C, Java, C/C++, php, and Typescript. For me, JavaScript is paused in my life of high school life from 2007 to 2010. I am glad to have Typescript which makes the whole development process much more easier with type checking. And, as I have been working on several data visualization projcets, I learnt ReactJs for UI/UX development. And, now, Typescript is my favourite programming language.

The npm package hk-bus-eta have become open source along with the project hkbus.app. I originally wrote the package in CommonJS for server-side compatability to older project, while the entire pattern differences from ECMAScript had really pissed me off. After several months, I decide to rewrite it in ECMAScript and keeping it compatible to both CommonJS and ECMAScript.

Taking a simple project cantonese-romanisation as an example, the following steps could publish the package to NPM.

0. TLDR;

Check out the directory structure and package.json in the example Git repository, and run the following commands.

1
2
3
4
5
6
7
# pull the project and prepare the environment
$ git clone https://github.com/chunlaw/cantonese-romanisation
$ cd cantonese-romanisation
$ yarn

# publish the package to NPM
$ yarn publish

1. Directory Structure

Below directory structure depicts the files required.

1
2
3
4
5
6
7
8
9
10
.
├── package.json        # required
├── README.md           # description to be used in NPM
├── tsconfig.json       # typescript configuration setting
├── README.md           # description to be used in NPM
├── src/                # the folder of source code 
│ └── index.ts          # source code entry point
├── jest.config.js      # optional, jest configuration setting
└── test/               # optional, just for testing the package before publish
  └── index.test.ts     # test cases for jest running 

2. Config package.json

Assume the package.json has already been initialized by yarn init or npm init. We go through several important parameter belows.

The field name, version, description, author, license, homepage, repository, keywords, and bugs are the metadata for indicating other developers what the package is.

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
{
  ...
  "name": "cantonese-romanisation",
  "version": "1.0.5",
  "description": "Library for mapping Chinese character to Hong Kong Government Cantonese Romanisation, Pingyam (Yale or LSHK)",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chunlaw/cantonese-romanisation.git"
  },
  "bugs": {
    "url": "https://github.com/chunlaw/cantonese-romanisation/issues"
  },
  "homepage": "https://cantonese-romanisation.chunlaw.io",
  "keywords": [
    "cantonese",
    "pingyam",
    "pingyum",
    "pinyin",
    "chinese",
    "roman",
    "yale",
    "lshk",
    "Hong Kong Government Cantonese Romanisation",
    "romanisation"
  ],
  "author": "chunlaw",
  ...
}

3. Prepare environment

The field dependencies are the packages that are required for your application to run properly, and devDependencies are the packages that are required for development and testing purposes only. In this example, the dependencies is empty as the package is all fine without third party dependencies.

We will run the following command,

1
2
3
yarn add -D @types/jest csvtojson jest rimraf ts-jest ts-node typescript
# or 
npm install --save-dev @types/jest csvtojson jest rimraf ts-jest ts-node typescript

The package.json will be something like below,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  ...
  "dependencies": {
  },
  "devDependencies": {
    "@types/jest": "^29.5.4",
    "csvtojson": "^2.0.10",
    "jest": "^29.6.4",
    "rimraf": "^5.0.1",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "typescript": "^5.2.2"
  },
  ...
}

4. Writing Code and test case

The content of ./src/index.ts is as below.

1
2
3
4
// ./src/index.ts
export function sayHello(): void {
  return "Hello";
}

You may use run npx ts-node src/index.ts in your terminal to check if the program runs properly or not. In the above case, there should be empty output as there is no function call to sayHello().

The content of ./test/index.test.ts is as below,

1
2
3
4
5
6
// ./test/index.test.ts
import { sayHello } from "../src/index"

test('expected Hello', () => {
  expect(sayHello()).toBe("Hello")
})

Don’t forget to config the jest by jest.config.js as below,

1
2
3
4
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

5. Setup scritps

Add the following lines into package.json.

1
2
3
4
5
6
7
8
9
10
11
{
  ...
  "scripts": {
    "clean": "rimraf dist",
    "test": "jest",
    "build:esm": "tsc --target es2018 --outDir esm",
    "build:cjs": "tsc --target es2015 --module commonjs --outDir dist",
    "build": "yarn build:cjs && yarn build:esm"
  },
  ...
}

After that, build:esm will build the script for ECMAScript, and build:cjs for CommonJS

6. Entry point for CommonJS and ECMASCript

Add the following lines into package.json for setting up the entry points for CommonJS and ECMAScript.

1
2
3
4
5
6
{
  ...
  "main": "dist/index.js",
  "module": "esm/index.js",
  ...
}

7. Whitelisting publishing folders

Add the following lines to whitelist the bundle build folders and source code to be published in NPM. These folders will be submitted along with README.md to NPM.

1
2
3
4
5
6
7
8
9
{
  ...
  "files": [
    "dist",
    "esm",
    "src"
  ],
  ...
}

Right now, we have finished all the setup for package.json, the content should look similar as below.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{
  "name": "cantonese-romanisation",
  "version": "1.0.5",
  "description": "Library for mapping Chinese character to Hong Kong Government Cantonese Romanisation, Pingyam (Yale or LSHK)",
  "main": "dist/index.js",
  "module": "esm/index.js",
  "files": [
    "dist",
    "esm",
    "src"
  ],
  "scripts": {
    "clean": "rimraf dist",
    "test": "jest",
    "build:esm": "tsc --target es2018 --outDir esm",
    "build:cjs": "tsc --target es2015 --module commonjs --outDir dist",
    "build": "yarn build:cjs && yarn build:esm"
  },
  "keywords": [
    "cantonese",
    "pingyam",
    "pingyum",
    "pinyin",
    "chinese",
    "roman",
    "yale",
    "lshk",
    "Hong Kong Government Cantonese Romanisation",
    "romanisation"
  ],
  "author": "chunlaw",
  "license": "MIT",
  "dependencies": {
  },
  "devDependencies": {
    "@types/jest": "^29.5.4",
    "csvtojson": "^2.0.10",
    "jest": "^29.6.4",
    "rimraf": "^5.0.1",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "typescript": "^5.2.2"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chunlaw/cantonese-romanisation.git"
  },
  "bugs": {
    "url": "https://github.com/chunlaw/cantonese-romanisation/issues"
  },
  "homepage": "https://cantonese-romanisation.chunlaw.io"
}

8. Build and submit

Run the following commands to build and submit to NPM

1
2
3
$ yarn build && yarn publish
# or
$ npm run build && npm publish

Have fun.

This post is licensed under CC BY-NC-ND 4.0 by the author.