Packaging uncompiled TypeScript code in a NPM module

Writing a node module in TypeScript and publishing it in its original form on NPM is not as straightforward as it should be, because this system was originaly designed for Javascript modules only.

The challenge is to properly package the typing information from your TypeScript code, that may get lost in the process if you don’t do it the right way.
That’s why I decided to put together the information I gathered while addressing the issue.

In this tutorial, I will explain the easiest way to package some TypeScript code into a node module, preserving all the typing information from the source code, without using any third-party tool.

You can download the full sample project on my GitHub repo. It is a working example of a TypeScript package.

This tutorial is compatible with TypeScript 1.5.3. For TypeScript 1.6 and 1.7, you will need a definition file generator like dts-generator or autodts.

Why distributing original TypeScript source code and not compiled Javascript?

Note: I will not get into details about the package.json, as it is exactly the same as a Javascript project.

Sample code

The package I’ll be building in this tutorial will be very simple, consisting of two basic classes:

export default class MyClass {
   constructor() {
      console.log("Hello world!");
   myMethod(myClassS: string): number {
      return 2;
export default class AnotherClass {
   doSomething(param: string) {
      console.log("Doing something...");

Index file

The first thing you need to have in your package is an index.js file, that will allow your module to be loaded with the require instruction (‘node_modules/my_module/index.js‘).

We’ll use this file to import all the components of our library that we want to expose to the user:

import MyClass from './MyClass';
import AnotherClass from './AnotherClass';

// an example of default export
export default MyClass; 
// an example of named exports
export {

Note: you’ve probably noticed that I used the default and named exports. I will soon write a post about it to explain how it works, so stay tuned! 😉

Typing definitions

To be able to use the package as a TypeScript module and benefit from the type checking, you must create a definition file that will be used by your users’ TypeScript compiler.

To do so, create a file with the .d.ts extension, typings.d.ts for instance, and fill it with the following:

/// <reference path="index.ts" />
declare module 'my_module' {
    // Put this if there is a default export in your module
    import defaultExport from 'index';
    export default defaultExport;

   // Put this if there are named exports in your module
   export * from 'index';

Note: You can also use the “old school” require syntax in this declaration:

/// <reference path="index.ts" />
declare module 'my_module' {
    import index = require('index');
    export = index;

This typings.d.ts file will allow the compiler to link the require instruction in your user’s code to the typings defined in the TypeScript files of your library.

As you probably noticed, this file does not have any coupling to your actual library, it just references the index file that any node module is expected to have.

Packaging everything

Your users will have to reference the .d.ts file that you just created, so don’t forget to include it in your release!

Let’s recap the minimal content of your package:

  • package.json (you already know what to put inside it!)
  • source files: MyClass.ts, AnotherClass.ts
  • a main file: index.ts
  • bundled typing definitions: typings.d.ts

What about compiled Javascript files?

Thanks to the require chain starting from the typings.d.ts file that the user references, all your project should be compiled automatically by the user’s compiler, so there’s no need to provide .js files.

Using your packaged module

To use your package, your fellow developers will need to both

/// <reference path="node_modules/your_package/typings.d.ts">



Example: the following script will load the module we just created. The module files are stored in node_modules/my_module to simulate an npm install setup. You can download the full project on my GitHub Repo.

///<reference path="node_modules/my_module/typings.d.ts" />
import myModule = require('my_module');

var obj = new myModule.MyClass();

var obj2 = new myModule.AnotherClass();

The reference and the require are both mandatory to get both the code and the typings information in a project.

If there’s no reference to typings.d.ts, the users will have to manually add your module to the TypeScript compiler file list when compiling their project.

I’ve tested this project in WebStorm 11 and, so far, autocompletion works seamlessly.
Still, beware that the way you declare your exports in your library may break the autocompletion user-side. I will soon write a post about it.

Compiled Javascript files VS. TypeScript sources

I spent quite some time figuring out if I should better distribute the source files and let the TypeScript compilation be part of the build process or if I should distribute compiled Javascript files with typing definitions (.d.ts files).

It would probably have been possible to distribute the compiled Javascript files but it would have required the use of a third-party tool to generate a global .d.ts file containing the definitions of all the classes and functions used throughout your project. Unfortunately, the generation of this global .d.ts file is not implemented yet in the TypeScript compiler.

As you have probably experienced, distributing a TypeScript module can get tricky, so I sat down and tried to figure out what exactly what was going on. In the end, the solution is not that complicated (only one additional file is needed) and highlights, once again how flexible this technology is.

Bonus: why is typings.d.ts needed?

For those who are interested about how things works internally, I will spend some time here explaining the need of this additional file.

To be able to require your module using the require(‘my_module’) syntax, an ambiant module with the same name has to be declared.
Ambient modules are declared with a quoted name, like this:

declare module 'my_module' {
   // ... module content goes here

Unfortunately, this declaration can’t be included in your index.ts, because the index is used to load all our external modules (import MyClass from ‘./MyClass’), which is incompatible with ambient module declarations.
As the compiler won’t find any ambient module declaration, your users’ require instruction will fail to compile.

You can’t either write only an ambient module declaration in index.ts because the exports will be empty and your users will get the classic “undefined is not a function” when using your lib.

Packaging uncompiled TypeScript code in a NPM module was last modified: September 1st, 2015 by Tom Guillermin

2 thoughts on Packaging uncompiled TypeScript code in a NPM module

  1. Have you tried doing this with Typescript 2.0? I am working with it now. I can get it to work to import the files and work but I haven’t found a way to get auto-completion to work in WebStorm 11.

Leave a Reply