Skip to Content
JavaScript Advanced: Code That Holds Up in Production
Advanced
0 likes
Courses
JavaScript Advanced: Code That Holds Up in Production

Bundling With Vite and Publishing to npm

Build a small library with Vite, set up package.json exports and files, check the tarball with npm pack, and publish safely. After this, people install exactly the files you meant to ship.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If your package already builds and you only want the pre-publish checks, skip to "See exactly what ships".

Publishing to npm takes one command. Publishing the right thing takes a few more. Once a version is out, you can't change it. You can only unpublish it under npm's limits (freely in the first 72 hours, and not once other packages depend on it), and that version number can never be used again. So the work is in the checks before npm publish, not in the publish itself.

The example here is a tiny library, @xtensyon/peso, that formats centavo amounts as pesos. It was never published. Every output below came from npm run build, npm pack and npm publish --dry-run.

The source

// src/index.js
const formatter = new Intl.NumberFormat("en-PH", { style: "currency", currency: "PHP" });

/** @param {number} centavos */
export function formatPeso(centavos) {
  return formatter.format(centavos / 100);
}

/** @param {number[]} amounts */
export function sumCentavos(amounts) {
  return amounts.reduce((sum, amount) => sum + amount, 0);
}

Build it with Vite

Node can run this file as it is. You bundle a library when you want to ship one file per format, support projects that still use require(), or strip things like JSX and TypeScript. Vite's library mode does that in a few lines:

npm install --save-dev vite
// vite.config.js
import { defineConfig } from "vite";

export default defineConfig({
  build: {
    lib: {
      entry: "src/index.js",
      name: "Peso",
      fileName: "peso",
      formats: ["es", "cjs"],
    },
  },
});

npm run build (with "build": "vite build" in package.json), on Vite 8.3:

vite v8.3.4 building client environment for production...
transforming...
✓ 2 modules transformed.
rendering chunks...
computing gzip size...
dist/peso.js  0.26 kB │ gzip: 0.20 kB

transforming...
✓ 2 modules transformed.
rendering chunks...
computing gzip size...
dist/peso.cjs  0.26 kB │ gzip: 0.21 kB

✓ built in 47ms

Two files: peso.js for import, peso.cjs for require.

package.json fields that matter

{
  "name": "@xtensyon/peso",
  "version": "0.1.0",
  "description": "Format and add Philippine peso amounts stored as centavos.",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/peso.cjs",
  "module": "./dist/peso.js",
  "exports": {
    ".": {
      "import": "./dist/peso.js",
      "require": "./dist/peso.cjs"
    }
  },
  "scripts": {
    "build": "vite build",
    "prepublishOnly": "npm run build"
  },
  "license": "MIT"
}
  • name: a scope like @xtensyon/ avoids name clashes and shows who owns it.
  • version: follow semver. Breaking change, bump the first number.
  • exports: the only entry points people can import. Anything not listed can't be imported, which lets you change internal files later without breaking anyone.
  • files: an allow-list of what goes into the package. This is the most important line in the file.
  • prepublishOnly: builds right before publishing, so you can't publish a stale dist.

See exactly what ships

npm pack --dry-run lists every file that would be uploaded, without uploading anything:

npm notice 📦  @xtensyon/peso@0.1.0
npm notice Tarball Contents
npm notice 17B README.md
npm notice 262B dist/peso.cjs
npm notice 265B dist/peso.js
npm notice 500B package.json
npm notice Tarball Details
npm notice name: @xtensyon/peso
npm notice version: 0.1.0
npm notice filename: xtensyon-peso-0.1.0.tgz
...
npm notice total files: 4

Four files. Now the same project with the files line removed:

npm notice 22B .env
npm notice 17B README.md
npm notice 262B dist/peso.cjs
npm notice 265B dist/peso.js
npm notice 470B package.json
npm notice 341B src/index.js
npm notice 209B vite.config.js
npm notice 715B xtensyon-peso-0.1.0.tgz
npm notice total files: 8

The .env file with an API token is in there, and so is an old tarball. npm has a short built-in ignore list, but .env isn't on it. If the project has a .gitignore, npm uses it when there's no .npmignore, which is why this mistake often doesn't happen, until the one project where it does. Use files and stop depending on luck.

⚠ Warning: If a secret was ever published, unpublishing doesn't help. Mirrors and caches may already have it. Revoke and replace the secret first, then clean up the package.

Test it like a user would

npm pack (without --dry-run) writes the real .tgz. Install that into a separate empty project and import it:

npm pack
cd ../try-peso
npm install ../peso/xtensyon-peso-0.1.0.tgz
node --input-type=module -e 'import { formatPeso, sumCentavos } from "@xtensyon/peso"; console.log(formatPeso(sumCentavos([45000, 120050])));'
node -e 'const { formatPeso } = require("@xtensyon/peso"); console.log(formatPeso(99));'
₱1,650.50
₱0.99

Both import and require work, from the tarball, not from your source folder. That catches a wrong path in exports or a missing file in files before anyone else does.

Publish

npm publish --dry-run
npm login
npm publish --access public

Scoped packages are private by default, so the first publish needs --access public. Turn on two-factor authentication for your npm account. For publishing from CI, use npm's trusted publishing or a granular access token limited to that package, never your personal login.

💡 Tip: Add a CHANGELOG.md entry and a git tag for every version. When someone reports a bug in 1.4.2, you'll want to know exactly what changed in it.

Common mistakes

  • No files field, shipping .env, tests and old tarballs.
  • Publishing without building, so dist is from last month.
  • An exports path that points to a file that isn't in the package.
  • A breaking change in a minor version.
  • Testing the package only from the source folder.

What happens if you skip this

Your first version works on your machine and fails for everyone who installs it, or worse, works fine and ships your token to everyone who installs it.

Run npm pack --dry-run on a project you've worked on. Is there a single file in the list you didn't expect?

About

Build a small library with Vite, set up package.json exports and files, check the tarball with npm pack, and publish safely. After this, people install exactly the files you meant to ship.

Duration
5 min
Last update
08/27/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.