Create an npm package template with TypeScript and rollup.js

0xkoji - Jan 27 '21 - - Dev Community

Here is my repo for this article.

npm-package-template

This is a template for a npm package.
Current src has trim and isOdd/isEven functionality.

How to run locally

$ yarn

# lint
$ yarn lint

# test
$ yarn test
Enter fullscreen mode Exit fullscreen mode

Circle CI

image: cimg/node:15.1
Circle CI image is using npm so need package-lock.json.




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.

$ mypackagetemplate 
$ cd mypackagetemplate
$ yarn init or npm init
Enter fullscreen mode Exit fullscreen mode

Install packages for the template

In this step, we install some packages for the template.

$ yarn add typescript --dev

# generate tsconfig.json
$ yarn tsc --init or node_modules/.bin/tsc --init
Enter fullscreen mode Exit fullscreen mode

Update generated tsconfig.json like below.

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Write code

$ mkdir src
$ cd src
$ touch index.ts
$ touch odd_even.ts
Enter fullscreen mode Exit fullscreen mode

odd_even.ts
The code is super simple. Just pass a number and if the number is even, it will return true.

export default (value: number): boolean => {
  return value %2 ==0 ? true : false;
}
Enter fullscreen mode Exit fullscreen mode

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

import odd_even from './odd_even';

export default {
  odd_even: odd_even,
}
Enter fullscreen mode Exit fullscreen mode

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.

$ 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
Enter fullscreen mode Exit fullscreen mode

You will see .eslintrc.js in your project folder

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": {
    }
};
Enter fullscreen mode Exit fullscreen mode

We need to modify package.json to run eslint.

  "scripts": {
    "build": "rollup -c",
    "lint": "eslint --fix 'src/**/*.ts'",
    "test": "jest"
  },
Enter fullscreen mode Exit fullscreen mode

Now we can do lint for codes under src.

$ yarn lint
Enter fullscreen mode Exit fullscreen mode

Then, add configs for jest to package.json

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

Write test

First, creatae a test folder

$ mkdir test
Enter fullscreen mode Exit fullscreen mode

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).

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);
  });
});
Enter fullscreen mode Exit fullscreen mode

Now we can run a test with the following command

$ yarn test
Enter fullscreen mode Exit fullscreen mode

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.

$ 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
Enter fullscreen mode Exit fullscreen mode

Add .babelrc.js

module.exports = {
  presets: [
    [
      "@babel/preset-env",
    ],
  ],
};
Enter fullscreen mode Exit fullscreen mode

rollup.config.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,
      }),
    ],
  },
];
Enter fullscreen mode Exit fullscreen mode

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)

$ yarn build
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .