How to Publish a Fully Production‑Ready TypeScript Package to npm
This step‑by‑step guide shows how to create, configure, test, and publish a TypeScript npm package using Git, Prettier, tsup, Vitest, GitHub Actions CI, and Changesets for versioning and release automation.
In this guide we start from an empty directory and walk through publishing a fully production‑ready npm package, covering Git version control, TypeScript, Prettier, export checks, building with tsup, testing with Vitest, CI with GitHub Actions, and versioning with Changesets.
Use Git for version control
Write code with TypeScript for type safety
Format code with Prettier
Check exports with @arethetypeswrong/cli Compile TypeScript to CJS and ESM with tsup
Run tests with Vitest
Run CI with GitHub Actions
Version and publish with Changesets
.1:Initialize Repository
Run the following command to create a new Git repository:
git init1.2:Set .gitignore
Create a .gitignore file at the project root and add:
node_modules1.3:Create Initial Commit
Run the following commands to make the first commit:
git add .
git commit -m "Initial commit"1.4:Create a New Repository on GitHub
Using the GitHub CLI, create a new repository (example name tt-package-demo):
gh repo create tt-package-demo --source=. --public1.5:Push to GitHub
Push your code to GitHub: git push --set-upstream origin main Next we will create a package.json, add a license, a LICENSE file, and a README.md file.
2.1:Create package.json File
Create a package.json with the following content:
{
"name": "tt-package-demo",
"version": "1.0.0",
"description": "A demo package for Total TypeScript",
"keywords": ["demo", "typescript"],
"homepage": "https://github.com/yourgithub/tt-package-demo",
"bugs": {"url": "https://github.com/yourgithub/tt-package-demo/issues"},
"author": "Matt Pocock <[email protected]> (https://totaltypescript.com)",
"repository": {"type": "git", "url": "git+https://github.com/yourgithub/tt-package-demo.git"},
"files": ["dist"],
"type": "module"
} nameis the npm package name (must be unique). version follows semver, e.g., 0.0.1. description and keywords help with npm search. homepage points to the repo or docs. bugs URL for issue reporting. author identifies you; you can add contributors if needed. repository links to the GitHub repo. files lists files to include when publishing (here dist). type set to module indicates ESM.
2.2:Add license Field
Add a MIT license field to package.json:
{
"license": "MIT"
}2.3:Create LICENSE File
Create a LICENSE file with the MIT license text (replace [year] and [fullname] with the current year and your name).
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
... (full license omitted for brevity) ...2.4:Create README.md File
Create a README.md describing the package, e.g.:
**tt-package-demo**
A demo package for Total TypeScript.3.1:Install TypeScript
Install TypeScript as a dev dependency:
npm install --save-dev typescript3.2:Set Up tsconfig.json
Create a tsconfig.json with these options (core options shown):
{
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"module": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"sourceMap": true,
"declaration": true,
"declarationMap": true
}
}3.3:Configure DOM Types (optional)
If your code runs in the browser, skip this step. Otherwise add:
{
"compilerOptions": {
"lib": ["es2022"]
}
}3.4:Create a Source File
Create src/utils.ts:
export const add = (a: number, b: number) => a + b;3.5:Create an Index File
Create src/index.ts:
export { add } from "./utils.js";3.6:Set build Script
Add a build script to package.json:
{
"scripts": {
"build": "tsc"
}
}Running npm run build compiles the TypeScript to JavaScript in dist.
3.7:Run Build
npm run build3.8:Add dist to .gitignore
dist3.9:Set ci Script
{
"scripts": {
"ci": "npm run build"
}
}4.1:Install Prettier
npm install --save-dev prettier4.2:Create .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2
}4.3:Add format Script
{
"scripts": {
"format": "prettier --write ."
}
}4.4:Run format Script
npm run format4.5:Add check-format Script
{
"scripts": {
"check-format": "prettier --check ."
}
}4.6:Add check-format to CI
{
"scripts": {
"ci": "npm run build && npm run check-format"
}
}5.1:Install @arethetypeswrong/cli
npm install --save-dev @arethetypeswrong/cli5.2:Add check-exports Script
{
"scripts": {
"check-exports": "attw --pack ."
}
}5.3:Run check-exports
npm run check-exportsInitially you will see resolution failures for Node and bundler.
5.4:Add main Field
{
"main": "dist/index.js"
}5.5:Run check-exports Again
npm run check-exportsNow only a warning for Node 16 (CJS) remains.
5.6:Fix CJS Warning (optional)
If you do not want to support CJS, change the script to ignore the rule:
{
"scripts": {
"check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm"
}
}5.7:Add check-exports to CI
{
"scripts": {
"ci": "npm run build && npm run check-format && npm run check-exports"
}
}6.1:Install tsup
npm install --save-dev tsup6.2:Create tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entryPoints: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
outDir: "dist",
clean: true,
});6.3:Change build Script to Use tsup
{
"scripts": {
"build": "tsup"
}
}6.4:Add exports Field
{
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/index.js",
"default": "./dist/index.cjs"
}
}
}6.5:Run check-exports Again
npm run check-exportsAll checks should now be green.
6.6:Use tsup as Linter (add noEmit )
Add noEmit": true to compilerOptions in tsconfig.json to let TypeScript only type‑check.
6.6.2:Remove Unused Fields
Remove outDir, rootDir, sourceMap, declaration, and declarationMap from tsconfig.json.
6.6.3:Change module to Preserve
{
"compilerOptions": {
"module": "Preserve"
}
}This allows imports without the .js extension.
6.6.4:Add lint Script
{
"scripts": {
"lint": "tsc"
}
}6.6.5:Add lint to CI
{
"scripts": {
"ci": "npm run build && npm run check-format && npm run check-exports && npm run lint"
}
}7.1:Install vitest
npm install --save-dev vitest7.2:Create Test File
import { add } from "./utils";
import { test, expect } from "vitest";
test("add", () => {
expect(add(1, 2)).toBe(3);
});7.3:Add test Script
{
"scripts": {
"test": "vitest run"
}
}7.4:Run Tests
npm run test7.5:Add dev Script for Watch Mode
{
"scripts": {
"dev": "vitest"
}
}7.6:Add test to CI
{
"scripts": {
"ci": "npm run build && npm run check-format && npm run check-exports && npm run lint && npm run test"
}
}8.1:Create GitHub Actions Workflow
name: CI
on:
pull_request:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install
- name: Run CI
run: npm run ci9.1:Install @changesets/cli
npm install --save-dev @changesets/cli9.2:Initialize Changesets
npx changeset init9.3:Make Changesets Public
Edit .changeset/config.json to set "access": "public".
9.4:Enable Automatic Commit
Set "commit": true in .changeset/config.json.
9.5:Add Local Release Script
{
"scripts": {
"local-release": "changeset version && changeset publish"
}
}9.6:Run CI Before Publishing
{
"scripts": {
"prepublishOnly": "npm run ci"
}
}9.7:Add a Changeset
npx changesetMark the version as patch and describe it (e.g., "initial release").
9.8:Commit Changes
git add .
git commit -m "Prepare for initial release"9.9:Run Local Release Script
npm run local-releaseThis runs the CI, versions the package, and publishes it to npm.
9.10:View Package on npm
http://npmjs.com/package/<your-package-name>You should now see your published package.
Summary
You now have a fully configured TypeScript package with Prettier, export checks, tsup compilation, Vitest testing, GitHub Actions CI, and Changesets for versioning and publishing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
