NPM Cheat Sheet

NPM Logo

Package Creation Steps

1. Git, NPM, Files

  1. GitHub: Create a new repository using github.com
    • repo name should be kebab case, i.e package-name, instead of snake_case or camelCase.
    • add description
    • choose public
    • Initialize with a README
    • Choose a license: MIT
    • don't create .gitignore file.
  2. Local: Clone repo git clone <package repo url>
  3. Local: Create .gitignore file
node_modules
lib

I prefer lib for libraries and CLI tools. I prefer dist for sites. YMMV.

  1. Local: Initialize project package.json file as a Node package
    • npm init
    • choose "MIT" for license to match what is in the repo
    • choose 0.0.6 for initial version (1.0.0 for first 'release')
    • choose defaults for others
  2. Local: Edit package.json
    • fix up fields, such as 'description'
    • replace author field with an object with more information.
    • add sideEffects:false if you won't have sideEffects
{
  "sideEffects": false,
  "author": {
    "name": "John Pennock",
    "email": "info@pennockprojects.com",
    "url": "https://pennockprojects.com"
  }
}

Side Effects The "sideEffects" key is used when a module affects something outside of the module, like a browser variable. Typically we don't want sideEffects and setting to false gives us benefits in bundling

  • "sideEffects": false
  • "sideEffects": ["someModuleWithSideEffects", "someOtherModuleWithSideEffects"]
  • "sideEffects": ["*.js"]
  1. Choose NPM Name
    1. Decide Scoped (Preferred) or Non-Scoped
      • Scoped Packages
        • @npm_user_name/some-cool-package-name
        • kebab casing
        • private by default (costs money at npmjs.org)
        • easy to avoid duplicates can match GitHub repo name
        • can be public if npm publish --access public when publishing
      • Non-scoped Packages
        • some-cool-package-name
        • kebab casing
        • public always
        • search npmjs.org for duplicates
        • must be a unique
    2. Update package.json "name" field to your NPM package name.
  2. Directories
    1. ./src - package source file root
    2. ./src/utils - package source files
    3. ./lib - output directory for packages/APIs
    4. ./tests - test directory
  3. Create skeleton files.
  • Library entrance point file ./src/index.ts
export { helloWorld } from './utils/helloWorld'
  • Library Function file, i.e. ./src/util/helloWorld.ts
export function helloWorld(name: string): string {
  return `Hello, ${name || "World"}!`;
}

2. Setup TypeScript

  1. Local: install Typescript in package as dev dependency
  • npm install --save-dev typescript @types/node
  1. Local: create tsconfig.json manually or use TypeScript CLI npx tsc --init

Here is the recommended tsconfig.json options

{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "lib/types",
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "outDir": "lib",
    "resolveJsonModule": true,
    "skipDefaultLibCheck": true,
    "sourceMap": true,
    "target": "esnext"
  },
  "include": [ "src" ],
  "exclude": [
    "node_modules",
    "lib"
  ],
}

3. Setup Bundler Rollup

A. Install Rollup with TypeScript and Delete Plugins

  • rollup-plugin-typescript2 - allows for typescript support
  • rollup-plugin-delete - deletes lib directory on each build
npm install --save-dev rollup rollup-plugin-typescript2 rollup-plugin-delete

B. Install Rollup plugins

  • @rollup/plugin-node-resolve - find node_modules packages
  • @rollup/plugin-json - converts .json files to ES6 modules
  • @rollup/plugin-commonjs - converts CommonJS modules to ES6
npm install --save-dev @rollup/plugin-node-resolve @rollup/plugin-json @rollup/plugin-commonjs

C. Create a rollup configuration file rollup.config.js

- creates both COMMONJS `cjs` and ESM `esm` library modules
import commonjs from '@rollup/plugin-commonjs';
import del from 'rollup-plugin-delete';
import json from '@rollup/plugin-json';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';

export default {
  input: 'src/index.ts',
  output: [
    {
      file: 'lib/index.cjs',
      format: 'cjs',
      sourcemap: true,
      inlineDynamicImports: true, 
    },
    {
      file: 'lib/index.esm.js',
      format: 'es',
      sourcemap: true,  
      inlineDynamicImports: true,
    }
  ],
  plugins: [
    del({ targets: 'lib/*' }),
    json(),
    commonjs(),
    nodeResolve({ browser: false, preferBuiltins: true }),
    typescript({ useTsconfigDeclarationDir: true })
  ]
}

D. Peer Dependencies (Optional)

Add package.son peerDependencies keys to rollup.config.js

const packageJson = require("./package.json");

const external = [...Object.keys(packageJson.peerDependencies || {})];

export default {
  // ...
  external
};

E. Update package.json for rollup.

  • update "main" to "main": "lib/index.cjs"
  • update "scripts" to include "build": "rollup -c"
  • add type, exports, and files
  • the files key says which files in our repo should be bundled in the npm package. LICENSE, package.json, and README.MD are automatically included.

Here a snippet for both modified and new key:values for package.json, you will have to merge manually with existing package.json file.

{
  "type": "module",
  "main": "lib/index.cjs",
  "exports": {
    "import": {
      "types": "./lib/types/index.d.ts",
      "default": "./lib/index.esm.js"
    },
    "require": {
      "types": "./lib/types/index.d.ts",
      "default": "./lib/index.cjs"
    }
  },
  "files": [ "lib" ],
  "scripts": {
    "build": "rollup -c"
  }
}

F. Smoke Test

Build everything and make sure it works.

  • npm run build

validate library files generated (both cjs and esm)

  • ./lib/index.cjs
  • ./lib/index.cjs.map
  • ./lib/index.esm.js
  • ./lib/index.esm.js.map

validate types

  • ./lib/types/index.d.ts
  • ./lib/types/utils/helloWorld.d.ts

4. CLI (Optional)

To add a CLI to a library, here are my steps

A. Install Version injector plugin

This synchronizes the CLI version with the package.json version.

npm install --save-dev rollup-plugin-version-injector

B. Update rollup.config.js

Here are the CLI changes to rollup. NOTE export default becomes an array as it has two outputs -- CLI and Library -- instead of one.

// ...
import versionInjector from 'rollup-plugin-version-injector';

// NOTE!!!! the array multiple outputs
export default [
  // Library Configuration
  {
    //
  },

  // CLI Configuration
  {
    input: 'src/cli.ts',
    output: {
      file: 'lib/cli.js',
      format: 'es',
      sourcemap: true,
      banner: '#!/usr/bin/env node',
      inlineDynamicImports: true,
    },
    plugins: [
      commonjs(),
      json(),
      nodeResolve({ browser: false, preferBuiltins: true }),
      typescript({ useTsconfigDeclarationDir: true }),
      versionInjector({
        format: 'esm',
        injectInComments: false,
      }),
    ],
    external: ['fs', 'path'], // Mark Node.js built-ins as external
  },
];

C. CLI Arguments Commander

Install Commander, one of many but the most straightforward for me, for CLI command line argument and options processing

  • npm install commander

D. CLI Entry File

Create the entry file for the CLI ./src/cli.ts

In your ./src/cli.ts process input. Here is a skeleton file example.

import { helloWorld } from "./utils/helloWorld";
import { program } from "commander";

program
  .name("my-cli")
  .description("A skeleton CLI to say hello world")
  .usage("<name> [options]")
  .helpOption("-h, --help", "Display help for command")
  .version("[VI]{version}[/VI]", "-v, --version", "Display version information");

program
  .argument("<name>", "name to greet", "World")
  .action((name) => {
    console.log(helloWorld(name));
  });

program.parse(process.argv);

D. Update packgae.json

Add the bin to name your CLI and connect it your built file. Your name can be anything, my-cli shown here, but in practice it should be the NPM and repo name.

{
  "bin": {
    "my-cli": "./lib/cli.js"
  },
}

E. Quick Test

Build everything with:

  • npm run build

Smoke test using npx my-cli

> npx my-cli   
Hello, World!

> npx my-cli john
Hello, john!

> npx my-cli john foo 
error: too many arguments. Expected 1 argument but got 2.

> npx my-cli -h      
Usage: my-cli <name> [options]

A skeleton CLI to say hello world

Arguments:
  name           name to greet (default: "World")

Options:
  -v, --version  Display version information
  -h, --help     Display help for command

> npx my-cli --version
0.6.0

5. Setup Unit Tests

Steps for using Vitest as the test framework:


A. Install Vitest

Run the following command to install Vitest and its peer dependencies:

npm install --save-dev vitest @vitest/coverage-v8

B. Update package.json

Add a script to run Vitest in your package.json:

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest --coverage",
  }
}

C. Configure Vitest

Create a vitest.config.ts file in the root of your project:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true, // Enable global test functions like `describe`, `it`, etc.
    environment: 'node', // Use Node.js environment for testing
    coverage: {
      include: ['src/**/*.{js,ts}'], // Include JS/TS files in the src directory
      exclude: [
        'src/**/*.d.ts', // Exclude TypeScript declaration files
        'src/cli.ts', // Exclude CLI entry point
        'src/index.ts', // Exclude main entry point if it doesn't contain testable code
        'src/utils/*.test.ts', // Exclude test files from coverage
      ],
      provider: 'v8', // Use v8 for coverage reporting
      reporter: ['text', 'html', 'json-summary', 'json'],
      reportOnFailure: true, // Optional: Report coverage even if tests fail
      reportsDirectory: './tests/output/coverage', // Directory to output coverage reports
    },
  },
});

D. Create Test Folders

  • ./tests
  • ./tests/output
  • ./tests/output/coverage
  • ./tests/input

E. Add Test Output to .gitignore

I like setting the ./tests/output outside of git so I can dump test results into it.

node_modules
lib
tests/output

F. Smoke the Unit Test

Create a test file in

  • ./tests folder
  • ./src next to the source file
  • name should be the same as source file except it ends with test.js
  • JavaScript for the test

For example ./src/utils/hellowWorld.test.js

import { describe, it, expect } from 'vitest';
import { helloWorld } from './helloWorld';

describe('helloWorld function', () => {
  it('should return "Hello, World!" when called with no arguments', () => {
    const result = helloWorld();
    expect(result).toBe('Hello, World!');
  });

  it('should retnurn "Hello, john!" when called with "john"', () => {
    const result = helloWorld('john');
    expect(result).toBe('Hello, john!');
  });

  it('should be a function', () => {
    expect(typeof helloWorld).toBe('function');
  });
});

G. Run Tests

Run the tests using the following commands:

  • Run all tests once
    npm run test
    
  • Run all tests with Watch:
    npm run test:watch
    
  • Run tests with coverage:
    npm run test:coverage
    

H. Verify Coverage (Optional)

After running npm run test:coverage, a coverage folder will be generated with detailed coverage reports.


Advantages of Using Vitest

  • Native ESM Support: Works seamlessly with your ESM-based TypeScript project.
  • Fast and Lightweight: Built on Vite, making it faster than traditional test frameworks.
  • TypeScript-Friendly: No additional configuration is needed for TypeScript.

6. Publish Manual Initial

Using a scoped name, i.e. @pennockprojects/ in the package.json name file, by default will be private. In order to make it public but also scoped run the following

  1. Make sure you have run npm run build successfully
  2. npm whoami - should show you logged into your npm account
    • If not, login in with npm login
  3. npm publish --dry-run for testing
  4. IMPORTANT npm publish --access public - make sure to use the public flag
  5. Check in code - git status, git commit, and git push

Package Changeset

1. Initialize Changesets

  1. Local: Open project in a side branch git checkout -b <sidebranchname>
  2. Local: Install package CLI
    • npm install --save-dev @changesets/cli
  3. Local: Initialize 'changeset' into project (creates .changeset directory and config), using CLI
    • npx changeset init
  4. Local: Update .changeset/config.json
    • change "access":"restrictred" to "access":"public"

2. Setup GitHub Action

A. Local Setup

  1. Local: Create a directories ./.github/workflows
  2. Local: Create a release.yml file in .github/workflows
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: checkout repository code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.REPO_NAME_CHANGESET_TOKEN }}
      - name: setup node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Install dependencies
        run: npm ci
      - name: Create release pull request
        id: changeset_action
        uses: changesets/action@v1
        with:
          publish: npm run release-package
        env:
          GITHUB_TOKEN: ${{ secrets.REPO_NAME_CHANGESET_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_CHANGESET_TOKEN }}

B. Add Release Script

Local: Add the release script to the package.json

{
  "scripts": {
    "release-package": "npm run build && npx changeset publish"
  }
}

C. Create GitHub Token

  1. GitHub: Account and Settings (upper right picture, not repo setting)
  2. GitHub: Developer Settings <> (left hand column down)
  3. GitHub: Personal Access Tokens and Tokens (classic)
  4. GitHub: Choose Generate new token and Generate new token (classic) button and dropdown
    • Add a Note, i.e. "{repo name} changeset token"
    • Choose Expiration
    • In Scopes - pick repo and workflows
  5. GitHub: Choose Generate Token button
  6. GitHub: Copy key created
    • Waring - will not see again

D. Add GitHub Token to Repo

  1. GitHub: In your repository Settings choose Secrets and Variables
  2. GitHub: Choose Actions (below)
  3. GitHub: Choose New Repository Secret button
  4. GitHub: Choose a SNAKE_CASE all caps name
  5. GitHub: Paste your GitHub Account Secret (from previous step)
  6. GitHub: Choose Create
  7. Local: Authenticate local git with new token
    • git remote set-url origin https://<GIT_TOKEN>@github.com/<USERNAME>/<REPOSITORY>.git
  8. Local: Update release.yml REPO_NAME_CHANGESET_TOKEN to the name your created (2 places)

E. Create NPM Token

  1. Npmjs.com: Choose Profile Pic dropdown (upper-right)
  2. Npmjs.com: Choose Access Tokens
  3. Npmjs.com: Choose Generate New Token button and choose Classic Token
  4. Npmjs.com: Choose a name like REPO_NAME_NPM_CS_TOKEN (this can be used for all packages or individual packages)
  5. Npmjs.com: Choose publish privilege
  6. Npmjs.com: Choose Create
  7. GitHub: In your repository Settings choose Secrets and Variables
  8. GitHub: Choose Actions (below)
  9. GitHub: Choose New Repository Secret button
  10. GitHub: Choose a SNAKE_CASE all caps name - use the same from Npmjs.org, i.e. NPM_REPONAME_CS_TOKEN
  11. GitHub: Paste your secret
  12. GitHub: Choose Save
  13. Local: Update release.yml NPM_CHANGESET_TOKEN to the name you created at GitHub, i.e. NPM_REPONAME_CS_TOKEN

F. Branch Protection Rules

  1. GitHub: Choose repo Settings tab on the repo page
  2. GitHub: Choose Branches
  3. GitHub: Choose Add classic branch protection rule
  4. GitHub: Type main into Branch name pattern
  5. GitHub: Check Require a pull request before merging
  6. GitHub: Uncheck: Require approvals - if only one contributor
  7. GitHub: Check Do not allow bypassing the above settings
  8. GitHub: Choose Create button

3. Publish Changeset Action

You just need to push the changes and the automatic builds and actions will follow.

  1. git status git commit -m "<comment>"
  2. Push with
  • git push origin <sidebranchname>
  1. then follow the GitHub Changeset Pull Request
  2. Note: For the first time adding changeset and you did not create a new version it will not to the NPM publish action.

Package Workflows

Feature Workflow

Feature Local Work

  1. Local: Get latest 'main'
    • git pull origin main or git pull if you have it setup
  2. Local: Create working side branch to 'main'
    • git checkout -b <new_feature_branch>
  3. Local: Develop Patch, Feature, or Revision
  4. Local: Test
    • npm run test - find unit test breaks early
    • npm run build - find build breaks early
  5. Local: Create Change Set
    • run npx changeset
      • note no 's', not 'changesets'
      • Choose 'patch' or 'minor' (typically 'major' is for prerelease)
      • Enter a description
      • The result creates a randomly named Change Set file in the /.changeset folder, for example busy-monkey-keys. This file documents version bump and description for the Change Set GitHub action later
      • important don't update package.json. Change Set is going to to all of this automatically.
  6. Local: Git add, commit and push changes, includes the generated changeset file.
    • git add .
    • git commit -m "commit message"
    • git push origin <new_feature_branch>

This will generate a Changeset Branch PR.

Feature GitHub PR

See GitHub Changeset Pull Request

Changeset Pull Requests

Changeset Branch PR

  1. GitHub: In Repository, open the 'Compare & pull request` button on the repo.
  2. GitHub: important On the 'Open a pull request' page, make sure the merge target, drop-down, on upper left, is
    • base: main for feature or new release
    • base: <prerelease branch> for prerelease work
  3. GitHub: Choose 'Create Pull Request' button
  4. GitHub: Review 'Files Changed' tab
    • Changeset file, random name, i.e. busy-monkey-keys, should be included
  5. GitHub: Choose 'Conversation' tab
  6. GitHub: Choose 'Merge pull request' button, then 'Confirm merge'
  7. GitHub: Choose 'Delete branch' button in the 'Pull request successfully merged' section
  8. GitHub: Choose the 'Actions' tab and you should see the recent action log
    • Open the 'release' yml job.
    • Open 'Create release pull request' section
    • Check at the bottom you should see a success message 'creating pull request'

This will automatically generate a Changeset NPM Publish PR

Changeset NPM Publish PR

Note you can wait and queue other Changeset Branch PRs before doing this.

Note2 it is usually named something like 'Version Packages #11'

  1. GitHub: Choose the 'Pull requests' tab and then open the Version Packages or similar.
  2. GitHub: Review 'Files Changed' tab
    • package.json file with the new version
    • CHANGE.md file updated with version and messages
    • Changeset random named file, i.e. busy-monkey-keys, should be deleted
  3. GitHub: Choose 'Conversation' tab
  4. GitHub: Choose 'Merge pull request' button, then 'Confirm merge'
  5. GitHub: Choose 'Delete branch' button in the 'Pull request successfully merged' section
  6. GitHub: Choose the 'Actions' tab and you should see the recent action log
    • Open the 'Release' build job.
    • Check 'Create release pull request' section, at there should be a success message 'success package published successfully'

This should have published a new version on npmjs.com

Pre-release Workflow

Enter Pre-release mode

  1. Local: one-time Git checkout to a pre-release branch
    • git checkout -b prerelease
  2. Local: one-time Enter change set pre-release mode
    • npx changeset pre enter alpha
      • Creates a new file pre.json in the ./.changeset folder where
        • mode: "pre" - change set mode
        • tag: "alpha" - name of pre-release 'alpha' (from the change set command.)
        • "initialVersions": {} with the version inherited in the pre-release branch
  3. Local: Update GitHub action file release.yml to add pre-release branch, i.e. prelease
on:
  push:
    branches:
      - main
      - prerelease
  1. Local: Git add, commit, push
    • i.e. git add ., git commit -m "commit message" and git push

Pre-release Local Work

  1. Local: from prerelease branch check-out new side branch to work on the next version
    • git checkout -b <prerelease branch name>
  2. Local: Work on code in pre-release side branch
  3. Local: Add a change set
    • npx changeset
      • choose major, description
  4. Local: Git add, commit, push to the pre-release side branch

Pre-release GitHub PR

  1. GitHub: Do GitHub Create Pull Request
    • merge target should be base: prerelease
  2. GitHub: Do GitHub Finalize Publish

Pre-release Validate Publish

  1. Local: switch branch back to prerelease branch from side branch
    • git checkout <prereelease>
  2. Local: Git pull to get latest in the prerelease branch
    • git pull origin prelease
  3. Local: Validate files are correct
    • pre.json file should change set name
    • The change set file itself is there.
    • package.json should have a version that says something like "2.0.0-alpha.0"
  4. Local: delete local branch
    • git branch -D <sidebranch>
  5. NPM: Check the 'Versions' tab, refresh, and see that there is at least 2 current tags.
    • current main
    • alpha version

Test Package Usage

  1. Local: In your test consumer application, i.e. 'example node with package application', run your application and it should be working with current version (not alpha)
  2. Local: Install the pre-release version
    • npm install @<npmusername>/<packagename>@<prerelease version>
    • i.e. npm install @pennockprojects/greeting-package@2.0.0.alpha-0
  3. Local: Test
    • Make any changes needed for the breaking change to application (maybe make a new side branch for the test application)
    • Restart your test consumer application,
    • Test for changes
  4. Local: Revert back to mainline version
    • npm install @pennockprojects/<packagename>@latest
    • i.e. npm install @pennockprojects/greeting-package@latest

Parallel Feature

  1. Local: Check out side branch from 'main' (not 'prerelease')
  2. Local: Get latest 'main'
    • git pull origin main
  3. Local: Make Changes
  4. Local: Run

Package Extras

Package Dependencies

  • "dependencies" - included with our package
  • "peerDependencies" - not included with our package in production, but assumed to be included elsewhere
  • "devDependencies" - only included during development

Processing CLI

Creating and publishing a command-line interface (CLI) tool as an npm package allows you to share your useful scripts with other developers and make them easily installable and runnable from the terminal. Here's a step-by-step guide to get your CLI tool packaged up:

  1. Project Setup and Initialization.
    • Create a directory: Start by creating a new folder for your CLI project and navigate into it using your terminal: mkdir my-cli && cd my-cli
    • Initialize the Node.js project: This will generate a package.json file to manage your project's dependencies and configurations: npm init -y.
  2. Writing the CLI script.
    • Create an entry point: Create a new JavaScript file (e.g., index.js or cli.js) that will contain the logic of your CLI tool.
    • Add the shebang: Include the following line at the very top of your entry point file (index.js or cli.js) to tell the operating system to execute it using Node.js: #!/usr/bin/env node.
    • Implement your CLI logic: This is where you'll write the code that performs the actions of your CLI tool. You can use libraries like chalk, cli, meow, Commander.js or Yargs to handle command-line argument parsing and make your CLI more robust and user-friendly.
  3. Configuring package.json for CLI execution
    • Define the "bin" field: Add a "bin" field to your package.json file. This tells npm to create an executable symbolic link (symlink) to your CLI script when the package is installed globally or locally with npm link.
"bin": {
  "my-cli-command": "./index.js"
}
  • Replace "my-cli-command" with the desired name users will type to run your CLI in the terminal.
  1. Making the script executable.
    • Set executable permissions: Grant executable permissions to your entry point file using the following command: chmod +x index.js.
  2. Testing the CLI locally
    • Link the package: Run npm link in your project directory. This creates a global symlink that allows you to test your CLI tool as if it were installed globally.
    • Run your CLI: Now you can test your CLI tool by typing the command name defined in your package.json's "bin" field (e.g., my-cli-command) in your terminal.
  3. Publishing your package to npm
    • Create an npm account (if you don't have one): You'll need an npm account to publish packages.
    • Login to npm: In your project directory, run npm login and follow the prompts to authenticate with your npm account.
    • Publish the package: Once logged in, use the npm publish command to publish your CLI tool to the npm registry.
    • Now your CLI tool is successfully packaged as an npm package and ready to be used by others! They can install it globally with npm install -g my-cli-command or use npx to run it without a global installation.

Package Testing

Create a test app to host the package as a consumer.


Testing Locally

NPM Link for testing package with a test app

  1. In the package project run npm link
  2. In the test app project run npm link <@npmusername>/<packaagename>

NPM Un-linking testing package

  1. In the package project run npm unlink -g
  2. In the test app project run:
    • npm unlink <@npmusername>/<packaagename> - unlinks the use of the local link
    • npm install <@npmusername>/<packaagename> - this uses published npmjs.com registry version

Front-end Testing App

Vite as Runner

To quickly create a Vite app with favorite framework:

  • with prompts: npm create vite@latest
  • React.ts: npm create vite@latest example-react-app -- -- template react.ts
  • Vue.ts: npm init vite@latest my-vue-app --template vue.ts

Vite Force Flag

Add the --force flag to avoid cache issues with the package to the run script in package.json

{
  "scripts": {
    "dev": "vite --force",
  }
}

Server Testing App

Express Node.js as Runner

expressjs.com

  1. Create three files to start
  • File #1: ./package.json
{
  "name": "express-app",
  "version": "1.0.0",
  "description": "",
  "main": "app.ts",
  "scripts": {
    "dev": "node dist/app.js",
    "build": "npx tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/express": "^4.17.21",
    "typescript": "^5.4.5"
  },
  "dependencies": {
    "express": "^4.19.2"
  }
}
  • File #2: ./tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist"
  },
  "lib": ["es2015"]
}
  • File #3: ./src/app.ts
import express from "express";
const app = express();
const port = 3000;

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  return console.log(`Express is listening at http://localhost:${port}`);
});
  1. Build App npm run build
  2. Preview App npm run dev
  3. Add package npm install @npmusername/packagename

Cheats

Clean Install

  • npm ci - for "clean install", similar to npm install but used during CICD for a fresh install and uses exact versions from the package-lock.json file.
    • package-lock.json and package.json must be identical, or it falls
    • does not change package*.* files
    • does not install individual modules

Running Many Scripts

You can define multiple scripts in your package.json file and run them in sequence or in parallel.

  • Use && (double ampersand) for sequential execution.
    • Some early versions of Windows Powershell did not support this syntax, but recent versions should.
  • Use & (single ampersand) for parallel execution.
    • Test carefully as commands earlier in the list may be run in background and you may potentially lose the output including any error messages (depending on your platform/shell).

Invoke chained scripts via npm run.

For example, to run pre-build, build_logic, post_build, and exit scripts in sequence, you can do:

npm run pre-build && npm run build_logic && npm run post_build && npm run exit

To run them in parallel, you can do:

npm run pre-build & npm run build_logic & npm run post_build & npm run exit

Run chained scripts via a script in package.json. For example, to run pre-build, build_logic, post_build, and exit scripts in sequence, you can add a build script in your package.json like this:

"scripts": {
    ...
    "build": "npm run pre-build && npm run build_logic && npm run post_build && npm run exit",
    "pre-build": "echo Pre-build step",
    "build_logic": "echo Building...",
    "post_build": "echo Post-build step",
    "exit": "echo Exiting..."
}

To execute the build script, you would run:

npm run build

You can change the && to & in the build script to run the commands in parallel.

Footnotes