Master the Craft
Becoming a proficient developer often involves not just using packages, but also creating and sharing your own. This interactive guide will walk you through the process of creating and publishing your own NPM packages, from a simple JavaScript module to a more robust TypeScript package with enhanced security through provenance. Use the navigation on the left to jump to any section.
The Package Lifecycle
Before we dive in, it's helpful to understand the high-level process of bringing a package to life. The journey typically follows these steps, which we'll explore in detail throughout this guide.
How to Create a JavaScript Package
Let's start with the fundamentals: a straightforward JavaScript package. This is the most common and simplest type of package to create, forming the foundation of the NPM ecosystem.
1. Initialize Your Project
First, create a new directory for your package, navigate into it, and initialize a new NPM project. The `npm init -y` command quickly creates a `package.json` file with default values.
mkdir my-js-package && cd my-js-package
npm init -y
2. Fine-Tune Your `package.json`
Your `package.json` file holds the metadata for your project. Update the default fields to accurately describe your package. Key fields include `name`, `version`, `description`, `main`, and `license`.
{
"name": "my-awesome-greeter",
"version": "1.0.0",
"description": "A simple package to greet users.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["greeting", "hello", "welcome"],
"author": "Your Name <you@example.com>",
"license": "MIT"
}
3. Write Your Code
Create the file specified as your `main` entry point. In this case, it's `index.js`. This file will contain the core logic of your package.
// index.js
function sayHello(name) {
return `Hello, ${name}! Welcome to the world of NPM packages.`;
}
module.exports = { sayHello };
4. Test It Locally
Before you publish, always test your package on your local machine. The `npm link` command creates a global symbolic link, allowing you to use your package in other local projects as if it were installed from the registry.
npm link
How to Create a TypeScript Package
For more complex projects, TypeScript offers the benefits of static typing, improving code quality and maintainability. Setting up a TypeScript package involves a build step to compile your code into JavaScript.
1. Initialize and Add Dependencies
Start by creating a new project and initializing it. Then, install TypeScript as a development dependency.
mkdir my-ts-package && cd my-ts-package
npm init -y
npm install typescript --save-dev
2. Configure TypeScript
Generate a `tsconfig.json` file. This configuration file tells the TypeScript compiler how to handle your project, including where to find source files and where to output the compiled JavaScript.
npx tsc --init
For a library, you should configure `tsconfig.json` to generate type declaration files (`.d.ts`) and output compiled code to a `dist` directory.
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
3. Update `package.json` for TypeScript
Adjust `package.json` for TypeScript. Point `main` to the compiled output, add a `types` field for type definitions, include a `build` script, and use the `files` property to ensure only the `dist` folder gets published.
{
"name": "my-typed-greeter",
"version": "1.0.0",
"description": "A simple TypeScript package to greet users.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc"
},
"files": ["dist"],
"author": "Your Name <you@example.com>",
"license": "MIT",
"devDependencies": {
"typescript": "^5.0.0"
}
}
4. Write Your TypeScript Code
Create a `src` directory and your `index.ts` file within it. This is where your typed source code will live.
// src/index.ts
export function sayHello(name: string): string {
return `Hello, ${name}! Welcome to the world of TypeScript NPM packages.`;
}
5. Build Your Package
Run your build script to compile the TypeScript code into JavaScript. This creates the `dist` directory with the files that will actually be distributed via NPM.
npm run build
Publishing Your Package
With your package created and tested, it's time to share it with the world. Publishing makes your package available for anyone to install and use in their own projects.
1. Log in to NPM
First, you'll need an NPM account. If you don't have one, create one at npmjs.com. Then, log in from your terminal:
npm login
2. Publish
Navigate to your package's root directory and run the `publish` command. If the package name is unique, it will be uploaded to the NPM registry.
npm publish
Publishing Scoped Packages
Scoped packages are a great way to namespace your work, especially for organizations. They have the format `@scope/package-name`. By default, your scope is your NPM username. To publish a public scoped package for free, use the `--access public` flag.
npm publish --access public
How to Provide Provenance
Package provenance provides a verifiable link between a published package, its source code, and its build instructions, significantly enhancing supply-chain security. This assures consumers that the package they are installing was built from a trusted source.
Prerequisites
- Your project must be in a public Git repository.
- You need to use a supported CI/CD provider like GitHub Actions.
Configure Your CI/CD Workflow
To publish with provenance, modify your CI/CD workflow. In your GitHub Actions workflow file, grant `id-token: write` permissions and add the `--provenance` flag to your `npm publish` command. Once published, your package page on npmjs.com will display a "Verified" badge.
# .github/workflows/publish.yml
name: Publish Package with Provenance
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # This is required for provenance
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build # If you have a build step
- run: npm publish --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}