Skip to Content
Course content

52: Publishing a Typed npm Package

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, a colleague of mine named Sarah spent an entire weekend building a sleek utility for handling complex API pagination. She was thrilled with the type safety she'd baked into it, and she pushed it to npm with a sense of pride. Ten minutes later, a senior dev from another team Slacked her: "Cool library, Sarah, but why do I have to use // @ts-ignore on every single function call?" Sarah was confused—the code was written in TypeScript! The problem was that while the source was typed, she hadn't provided the definitions. To the rest of the world, her package looked like plain, untyped JavaScript.

This is a classic mistake. When you publish a package, you aren't just sharing your code; you're sharing a contract. If you want your users to get that sweet autocomplete and compile-time validation you enjoyed while writing the library, you have to ship the type definitions alongside the compiled JavaScript. You can't just publish your .ts files because npm users (and Node.js) can't execute those directly.

Generating the Declaration Files

To fix the problem Sarah had, we need TypeScript to generate .d.ts files. These are "declaration" files—they contain only the type information, stripped of the actual logic. They act as a map that tells the consumer's editor exactly what types are being passed in and out of your functions.

Let's say we're building a small utility called date-range-helper. Your tsconfig.json needs two critical settings to make this work. First, declaration: true tells the compiler to output those .d.ts files. Second, you need an outDir (like dist or lib) so your compiled JS and your type definitions aren't cluttering up your src folder.

{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "declaration": true, 
    "outDir": "./dist",
    "strict": true
  },
  "include": ["src"]
}

When you run tsc, TypeScript will now mirror your source structure inside the dist folder. For every index.ts, you'll get an index.js (the logic) and an index.d.ts (the types). If you don't do this, your users are essentially flying blind.

Mapping the Package Entry Points

Once the files exist in your dist folder, you have to tell npm how to find them. A package.json file is more than just a list of dependencies; it's the directory for your package. Most developers are familiar with the main field, which points to the JavaScript entry point, but for a typed package, the types (or typings) field is just as important.

If you leave the types field out, TypeScript will try to guess where the definitions are by looking for a .d.ts file with the same name as your main file. That usually works, but being explicit prevents weird bugs in different IDEs. Here is how your package.json should look for our date-range-helper:

{
  "name": "@your-username/date-range-helper",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc"
  }
}

I added a files array there for a reason. By default, npm publishes everything in your folder. You don't want to ship your tsconfig.json, your tests, or your raw src files to the user. By specifying "dist", you ensure the package is lean and contains only what's necessary for the code to run and the types to be validated. I've found that keeping your published package small not only looks professional but also speeds up npm install times for everyone else.




📋 Practical Task

Build and Package the "string-case-converter" Utility

Your goal is to set up a project structure for a library called string-case-converter that allows users to convert strings to kebab-case or snake_case. You aren't actually publishing to the npm registry, but you must configure the project so that it is "publish-ready."

Requirements:

  • Create a src/index.ts file with a function convertCase(str: string, mode: 'kebab' | 'snake'): string.
  • Configure tsconfig.json to enable declaration file generation and output all compiled files to a dist folder.
  • Configure package.json with the correct main and types fields pointing to the compiled files in the dist folder.
  • Add a files array to package.json to ensure only the dist directory is included in the final bundle.
  • Run the build command and verify that both index.js and index.d.ts exist in the dist folder.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.