Publishing an npm package


Creating and publishing an NPM package can enhance your portfolio and allow you to share reusable code with the community. Recently I released my own first package, a manga web reader (github.com/Mangatsu/mangatsu-reader) that is still very much experimental. This guide will walk you through setting up a repository for a TypeScript project, as well as configuring the project with useful linting software, and finally how to build and publish your package to NPM.

Prerequisites

  • npm v10.0.0+
  • Internet connection

Step 0: Choose your license

As for npm packages, usually MIT (or ISC) is recommended for the straightforwardness and ease of use. Sometimes, depending on the use case, Apache 2.0 with patent grant or strong copyleft license like (A)GPL 3.0 might fit your needs as well. Be sure to remember to create the LICENSE (or COPYING) file!

Step 1: Setting up the NPM package

  1. Initialize your NPM package: Start by creating a new directory for your project and navigating into it. Then initialize your project using npm init.
    mkdir name-of-the-package
    cd name-of-the-package
    npm init -y
    This will generate a package.json file with default configurations.
  2. Initialize git with git init and create .gitignore file
  3. Install TypeScript and related dependencies: To get started with TypeScript, you need to install TypeScript as well as some type definitions + rimraf for cleaning up
    npm install --save-dev typescript @types/node rimraf
  4. Set up TypeScript configuration: Create a tsconfig.json file for your TypeScript configuration.
    {
      "compilerOptions": {
        /* Language and Environment */
        "target": "ES2022",
        "lib": ["ES2022", "DOM", "DOM.Iterable"],
        "types": ["node", "react"],
        "jsx": "react",
        "useDefineForClassFields": true,
        /* Modules */
        "module": "ESNext",
        "moduleResolution": "bundler",
        "allowImportingTsExtensions": false,
        "resolveJsonModule": true,
        /* JavaScript Support */
        "allowJs": false,
        /* Emit */
        "declaration": true,
        "outDir": "./lib",
        "declarationDir": "./lib",
        /* Interop Constraints */
        "isolatedModules": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        /* Type Checking */
        "strict": true,
        "noUnusedLocals": false,
        "noUnusedParameters": false,
        "noFallthroughCasesInSwitch": true,
        /* Completeness */
        "skipLibCheck": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "lib"]
    }
    This setup ensures that TypeScript compiles source files from the src directory and outputs them into the lib directory.

Step 2: Setting up linting (ESLint, Prettier, Husky)

  1. Install dev dependencies:
    npm install --save-dev prettier eslint prettier-eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
  2. Create configuration files: eslint.config.mjs:
    {
      "semi": false,
      "trailingComma": "all",
      "singleQuote": false,
      "tabWidth": 2,
      "useTabs": false,
      "printWidth": 120,
      "endOfLine": "lf"
    }

(Optional) Setting up Husky pre-commit hook for automatic linting

  1. Install Husky and lint-staged:
    npm install --save-dev husky lint-staged
    npx husky install
  2. Configure lint-staged: Add a lint-staged configuration to run Prettier and ESLint on staged files before committing. In your package.json, add the following:
    "lint-staged": {
      "*.{ts,tsx,js,jsx}": "eslint --cache --fix",
      "*.{ts,tsx,js,jsx,css,md}": "prettier --write"
    }
    "scripts": {
      "prepare": "husky"
    }
  3. Add pre-commit hook:
    echo "npx lint-staged" > .husky/pre-commit

Step 3: Setting up the build process

  1. Update the build script: Add a tsc, clean and build commands to your package.json using the TypeScript compiler (tsc).
    "scripts": {
      "tsc": "tsc -p tsconfig.json",
      "clean": "rimraf lib && rimraf build",
      "build": "npm run clean && npm run tsc"
    }
    Running npm run build will compile your TypeScript files from src to the lib directory.
  2. Copy assets after the build: If your package includes non-TypeScript files (e.g., images, JSON files, etc.), you can add a script to copy those assets to your lib folder. In package.json, add the following script:
    "scripts": {
      "copy-assets": "node -e \"require('fs').cpSync('./src/assets', './lib/assets', {recursive: true});\"",
    }
  3. Final build command
    "scripts": {
      "build": "npm run clean && npm run tsc && npm run copy-assets"
    }

Step 4: Publishing to package npm

  1. Login to NPM: npm login
  2. Update your package.json with metadata: Ensure your package.json has a valid name, version, description, main, license, and files field. The files field specifies which files are included in the package:
    {
      "name": "name-of-the-package",
      "version": "0.1.0",
      "description": "",
      "main": "./lib/index.js",
      "types": "./lib/index.d.ts",
      "type": "module",
      "files": [
        "lib/"
      ],
      "repository": {
        "type": "git",
        "url": "github.com/<user,org>/<name-of-the-package>"
      },
      "keywords": [
        "typescript"
      ],
      "author": "Marko Kristian Leinikka",
      "license": "MIT"
    }
    1. Publish the package: npm publish. If it's your first time publishing, you can use npm publish --access public to make it publicly accessible.

Conclusion

You've now created a TypeScript NPM package with linting, formatting, pre-commit hooks, and a build process that handles asset copying. Whether you're publishing utility functions or a full-fledged library, this setup will help ensure a smooth workflow and quality code.

By following this guide, you should now be able to confidently publish your TypeScript packages to NPM and manage a clean, maintainable codebase.


By Marko Leinikka

Word count: 742
4 min read