5 things to know when writing a TypeScript NPM module

Cause targeting Node.js and TypeScript in one module isn’t that easy.

Recently i started a library named spotify-graphql in TypeScript.
I wanted the library to be “importable” in Node.js(v6) and TypeScript.
Here are all the things to know that will save you hours.

Proposed Package structure

- package.json
- gulpfile.js
- lib/
- myFile.ts
- spec/
- myFile.spec.ts
- index.ts
- tsconfig.json

1. package.json

{
"name": "my-package",
"version": "0.0.0",
...
"main": "dist/index.js", // for import from nodejs environments
"typings": "definitions/index", // TypeScript definitions
"typescript": {
"definition": "definitions/index" // TypeScript definitions
},
...
"devDependencies": {
"@types/node": "~6.0.46", // needed for Node.js targeting
...
},
"scripts": {
"test": "gulp test",
"release": "standard-version" // amazing package, seriously !
},
...
"dependencies": {
"@types/jasmine": "^2.5.38" // need for using jasmine in TS !
}
}

2. tsconfig.json

{
"compilerOptions": {
"lib": ["es6"], // we target Node.js(v6)
"module": "commonjs", // export compatibility
"target": "es5", // we target Node.js(v6)
"moduleResolution": "node", // we target Node.js(v6)
"declaration": true, // generate TypeScript definitions
"rootDir": ".",
"outDir": "dist", // transpile JS in this directory
"types" : ["node", "jasmine"] // only use needed types
},
"include": [
"index.ts",
"lib/**/*",
"spec/**/*"
]
}

3. gulpfile.js

The gulpfile is dead-simple.

It just uses :
- gulp-typescript
- gulp-jasmine

gulp.task('build', function() {
const merge = require('merge2');
const tsProject = ts.createProject('tsconfig.json');
var tsResult = tsProject.src()
.pipe(tsProject());
return merge([
tsResult.dts.pipe(gulp.dest('./definitions')),
tsResult.js.pipe(
gulp.dest(tsProject.config.compilerOptions.outDir)
)
]);
});

the build task fetches the tsconfig.json compilerOptions and generate 2 things :

  • transpiled JS in outDir
  • - TypeScript definitions in ./definitions dir (only needed if tsconfig.json specify definitions: true)

4. the “index.ts story”

When doing the following :

import * from 'my-package'

tsc will start to search for a index.ts in node_modules and all possible directories.
I found that by running tsc with — traceResolution option.

This option print all the path looked by tsc during module resolution, this is very helpful when a NPM module is “missing”.

So, when creating a TypeScript NPM module, remember to place a index.ts file a the root of your structure

5. Testing in TypeScript

In order to use Jasmine with TypeScript, you’ll need 2 things :

  • import Jasmine typings
  • transpile code to JS before running your specs

For the typing, see the “package.json” section.

Transpiling code before running the specs is done in the gulpfile, let’s take a glance :

gulp.task('test:run', function() {
return gulp.src('dist/spec/**').pipe(jasmine())
});
// ...
gulp.task('test', [], function(cb) {
runSequence('clean', 'build', 'test:run', cb);
});

As seen in “gulpfile.js” section, the build task transpile all JS (as specified in tsconfig.json) and store it in dist/ folder.

All we have to do is to send file located in dist/spec/ to jasmine, and “tada !”


I’ve made a public repo for bootstraping a TypeScript package :
https://github.com/wittydeveloper/typescript-npm-module-bootstrap/

Feel free to leave a comment, and have fun !