Create an npm package template with TypeScript and rollup.js
Tags: typescript, jest, rollupjs, npm
Published: 2021-01-27 14:13:13

Here is my repo for this article.
{% github koji/npm-package-template %}

## What is npm?
>npm is a package manager for the JavaScript programming language. npm, Inc. is a subsidiary of GitHub, that provides hosting for software development and version control with the usage of Git. npm is the default package manager for the JavaScript runtime environment Node.js. Wikipedia

https://www.npmjs.com/

## For people who prefer to use `js` instead of `ts`.
In this article, I use typescript, but if you don't want to use it, you can skip things that are related to typescript such as installing typescript, generating tsconfig.json, and using ts extension and types in codes.

## Set up a template project
First, we need to create a folder for the package template and run `yarn init` or `npm init`. I think package.json that is generated by `npm init` can be better than yarn's one since it covers the most basic items.
```zsh
$ mypackagetemplate 
$ cd mypackagetemplate
$ yarn init or npm init
```

## Install packages for the template
In this step, we install some packages for the template.
```zsh
$ yarn add typescript --dev

# generate tsconfig.json
$ yarn tsc --init or node_modules/.bin/tsc --init
```

Update generated tsconfig.json like below.
```json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
```

## Write code

```zsh
$ mkdir src
$ cd src
$ touch index.ts
$ touch odd_even.ts
```

`odd_even.ts`
The code is super simple. Just pass a number and if the number is even, it will return `true`.
```ts
export default (value: number): boolean => {
  return value %2 ==0 ? true : false;
}
```

`index.ts`
The following code allows us to import the code like below.
`import { odd_even } from 'package-name'`
```ts
import odd_even from './odd_even';

export default {
  odd_even: odd_even,
}
```


## Add ESLint Jest
In this step, we will set up eslint and Jest.
We will install eslint and jest.
`yarn run eslint --init` command allows us to create a config file intractively.

```zsh
$ yarn add eslint jest ts-jest -D
$ yarn run eslint --init
# settings for this
# ❯ To check syntax and find problems
# ❯ JavaScript modules (import/export)
# ❯ None of these
# ? Does your project use TypeScript? › No / ❯ Yes
# ✔ Browser
# ✔ Node
# ❯ JavaScript
# ? Would you like to install them now with npm? › No / ❯ Yes
```

You will see `.eslintrc.js` in your project folder
```js
module.exports = {
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
    }
};
```

We need to modify `package.json` to run `eslint`.
```json
  "scripts": {
    "build": "rollup -c",
    "lint": "eslint --fix 'src/**/*.ts'",
    "test": "jest"
  },
```

Now we can do lint for codes under `src`.
```zsh
$ yarn lint
```

Then, add configs for jest to `package.json`

```json
"jest": {
    "moduleFileExtensions": [
      "ts",
      "js"
    ],
    "transform": {
      "^.+\\.ts$": "ts-jest"
    },
    "globals": {
      "ts-jest": {
        "tsconfig": "tsconfig.json"
      }
    },
    "testMatch": [
      "**/test/**/*.test.ts"
    ]
  }
```

## Write test
First, creatae a test folder

```zsh
$ mkdir test
```

`test/odd_even.test.ts`
The test code is also simple as well as `odd_even.ts`. The code tests 2 cases, odd number(1) & even number(2).

```ts
import odd_even from '../src/odd_even';

describe('test odd_even', (): void => {
  test('odd', (): void => {
    const resp: boolean = odd_even(1);
    expect(resp).toBe(false);
  });

  test('even', (): void => {
    const resp: boolean = odd_even(2);
    expect(resp).toBe(true);
  });
});
```

Now we can run a test with the following command
```zsh
$ yarn test
```

## Set up rollup.js
The final step is setting up rollup.js(including Babel).

rollup.js
>Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger and more complex, such as a library or application. It uses the new standardized format for code modules included in the ES6 revision of JavaScript, instead of previous idiosyncratic solutions such as CommonJS and AMD. ES modules let you freely and seamlessly combine the most useful individual functions from your favorite libraries. This will eventually be possible natively everywhere, but Rollup lets you do it today.

https://rollupjs.org/guide/en/

If you don't like to use rollup.js, you can use others such as webpack.


```zsh
$ yarn add rollup rollup-plugin-terser @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript tslib @babel/core @babel/preset-env -D
```

Add `.babelrc.js`
```js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
    ],
  ],
};
```

`rollup.config.js`
```js
import { terser } from "rollup-plugin-terser";
import pluginTypescript from "@rollup/plugin-typescript";
import pluginCommonjs from "@rollup/plugin-commonjs";
import pluginNodeResolve from "@rollup/plugin-node-resolve";
import { babel } from "@rollup/plugin-babel";
import * as path from "path";
import pkg from "./package.json";

const moduleName = pkg.name.replace(/^@.*\//, "");
const inputFileName = "src/index.ts";
const author = pkg.author;
const banner = `
  /**
   * @license
   * author: ${author}
   * ${moduleName}.js v${pkg.version}
   * Released under the ${pkg.license} license.
   */
`;

export default [
  {
    input: inputFileName,
    output: [
      {
        name: moduleName,
        file: pkg.browser,
        format: "iife",
        sourcemap: "inline",
        banner,
      },
      {
        name: moduleName,
        file: pkg.browser.replace(".js", ".min.js"),
        format: "iife",
        sourcemap: "inline",
        banner,
        plugins: [terser()],
      },
    ],
    plugins: [
      pluginTypescript(),
      pluginCommonjs({
        extensions: [".js", ".ts"],
      }),
      babel({
        babelHelpers: "bundled",
        configFile: path.resolve(__dirname, ".babelrc.js"),
      }),
      pluginNodeResolve({
        browser: true,
      }),
    ],
  },

  // ES
  {
    input: inputFileName,
    output: [
      {
        file: pkg.module,
        format: "es",
        sourcemap: "inline",
        banner,
        exports: "named",
      },
    ],
    external: [
      ...Object.keys(pkg.dependencies || {}),
      ...Object.keys(pkg.devDependencies || {}),
    ],
    plugins: [
      pluginTypescript(),
      pluginCommonjs({
        extensions: [".js", ".ts"],
      }),
      babel({
        babelHelpers: "bundled",
        configFile: path.resolve(__dirname, ".babelrc.js"),
      }),
      pluginNodeResolve({
        browser: false,
      }),
    ],
  },

  // CommonJS
  {
    input: inputFileName,
    output: [
      {
        file: pkg.main,
        format: "cjs",
        sourcemap: "inline",
        banner,
        exports: "default",
      },
    ],
    external: [
      ...Object.keys(pkg.dependencies || {}),
      ...Object.keys(pkg.devDependencies || {}),
    ],
    plugins: [
      pluginTypescript(),
      pluginCommonjs({
        extensions: [".js", ".ts"],
      }),
      babel({
        babelHelpers: "bundled",
        configFile: path.resolve(__dirname, ".babelrc.js"),
      }),
      pluginNodeResolve({
        browser: false,
      }),
    ],
  },
];
```

Finally, we can build. If everything goes well, you will see `dist` folder and 4 js files(hellotslib.cjs.js, hellotslib.es.js, hellotslib.js, and hellotslib.min.js)
```zsh
$ yarn build
```

Now you can add any functionalities that you want to bring to npm world via your npm package 😎