How to Publish a Fully Production‑Ready npm Package from Scratch
This step‑by‑step guide shows how to initialize a Git repository, configure TypeScript, Prettier, Vitest, CI with GitHub Actions, set up package metadata, use Changesets for versioning, and finally build and publish a production‑ready npm package.
In this tutorial we start from an empty directory and walk through publishing a fully production‑ready npm package, covering version control, TypeScript, formatting, testing, CI, and release automation.
Use git init to create a new repository.
Create a .gitignore containing node_modules .
Make the initial commit with git add . and git commit -m "Initial commit" .
Create a new GitHub repository (e.g., tt-package-demo ) and push the code.
1.5 Create core files
Generate package.json with fields such as name , version , description , keywords , homepage , bugs , author , repository , files , and type . Add a license field (MIT), a LICENSE file, and a README.md describing the package.
2. Install TypeScript
<code>npm install --save-dev typescript</code>Add a .prettierrc file with preferred formatting options and a format script in package.json that runs prettier --write . . Also add a check-format script that runs prettier --check . .
3. Configure the project
Create tsconfig.json with strict compiler options, enabling ES module interop, skipping lib checks, targeting es2022 , allowing JS, resolving JSON modules, and generating declarations.
Optionally add a lib entry when the code does not need DOM APIs.
4. Add source files
Create src/utils.ts exporting a simple add function, and src/index.ts re‑exporting it.
5. Build scripts
Add a build script that runs tsc (later replaced by tsup ), and a ci script that runs npm run build && npm run check-format && npm run check-exports .
6. Export validation
Install @arethetypeswrong/cli as a dev dependency and add a check-exports script ( attw --pack . ) to verify that the package exports resolve correctly for Node and bundlers.
Set the main field to dist/index.js and add an exports map pointing import to dist/index.js and default to dist/index.cjs . Include ./package.json in the exports map.
7. Switch to tsup
Install tsup and create tsup.config.ts defining entry points, output formats ( cjs and esm ), declaration generation, output directory, and clean build.
Update the build script to run tsup instead of tsc .
8. Linting
Add a lint script that runs tsc as a type‑checking linter, and include it in the ci script.
9. Testing with Vitest
<code>npm install --save-dev vitest</code>Create src/utils.test.ts testing the add function, add a test script ( vitest run ), a dev script ( vitest ) for watch mode, and extend the ci script to run tests.
10. GitHub Actions CI workflow
<code>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 ci
</code>11. Changesets for versioning
Install @changesets/cli , run npx changeset init , set access to public and commit to true in .changeset/config.json . Add a local-release script ( changeset version && changeset publish ) and a prepublishOnly script ( npm run ci ).
Create a changeset with npx changeset , commit the changes, and run npm run local-release to publish the package to npm.
Summary
The guide sets up a TypeScript project with Prettier, export validation, tsup bundling, Vitest testing, GitHub Actions CI, and Changesets for automated versioning and publishing a production‑ready npm package.
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.