TypeScript Package Deployment with TravisCI

Automatic deployment is cool.

Note: This document is a bit outdated and my own personal workflow has changed since this publication. If desired, I can make an article about my new environment.

TypeScript is a really cool solution. Having the ability to actually build your code and test it is like making your cake and eating it too. Of course this introduces the problem of “it compiled fine on my computer”. There are many different solutions to this (portable development environments, cross platform tooling, etc.) however, I prefer to use continuous integration & deployment to help solve this problem. CI/D is really easy (and free for open source projects) through Travis CI.

The end result gives us a cool page for library documentation:

It also gives us publishing to NPM:

And it gives us releases on GitHub:

At a high level, we want our CI solution to:

  • Build & test our code.
  • Generate & Deploy TypeDoc documentation
  • Publish to NPM package
  • Add new GitHub release.

First we are going to want a few files:

  • package.json — NodeJS Package Manager configuration file.
  • tsconfig.json — TypeScript configuration.
  • .travis.yml — TravisCI configuration
  • .nojekyll — Empty file. Prevents GitHub from using Jekyll when we go to deploy our TypeDoc site.
  • .gitignore — Ignore files from being pushed to Git(Hub).
  • dev/src/index.ts — Library entry point
  • dev/test/index.ts — Some tests
  • .npmignore — Similar to .gitignore but to ignore files from being served in the package.

I’ve been preferring putting my src and test files within a dev folder so that when typescript does a build, it maps directly from the dev to dist directory. Then they are the same depth in the file structure, which can make other things easier.

Next we are going to want a few devDependencies :

  • shx — cross platform shell commands
  • typedoc — TypeScript documentation generation
  • typescript — Duh.
  • npm-run-all — Easily run multiple NPM scripts sequentially or in parallel.
  • mocha & @types/mocha — Testing framework.

Install these by using npm install --save-dev <PACKAGE> .

Next we want to add some scripts to our package.json . I like to use NPM scripts as their own build tool for backend services so tailor to taste if you use things such as Gulp or Grunt.

We want to add some scripts to our package.json:

  • setup — Installs packages (and any other setup).
  • build — Runs the TypeScript compiler.
  • test — Builds code and runs tests.
  • test-only — Runs the test. (I typically like my main test script to build then run the test).
  • typedoc — Generate the TypeDoc content.
  • posttypedoc — Copy the .nojekyll file into the docs folder.

The most non-trivial part of this entire process is the posttypedoc and why we need to copy the .nojekyll file. In essence, TypeDoc likes to generate files with weird names. These weird names conflict with how GitHub Page’s Jekyll operates on the generated content. By copying this file into our docs folder and deploying our docs folder, we are telling GitHub to not use Jekyll and instead serve unmodified content.

This is an example of the devDependencies and scripts for my general JS/TS utility library: rlib:

{
"scripts": {
"setup": "npm install",
"build": "tsc",
"build-watch": "tsc --watch",
"test-only": "mocha dist/test/",
"pretest": "npm run build",
"test": "npm run test-only",
"clean": "shx rm -rf node_modules/ dist/ docs/",
"typedoc": "typedoc --out ./docs --mode modules --tsconfig ./tsconfig.json ./dev/src/",
"posttypedoc": "shx cp .nojekyll docs/.nojekyll"
},
"devDependencies": {
"@types/chai": "^4.0.4",
"@types/mocha": "^2.2.43",
"chai": "^4.1.2",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.1",
"rimraf": "^2.6.2",
"shx": "^0.2.2",
"typedoc": "^0.9.0",
"typescript": "^2.5.3"
}
}

In order to properly serve our code and types, we must define a main and types section:

{
...
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.js",
...
}

Next, we have the NPM ignore file.

We want to make sure we explicitly do not include the generated docs or the source code. We also don’t want to include a zipped version of our distributed code that we will be deploying to GitHub releases later.

tsconfig.json
.travis.yml
.nojekyll
dev/
docs/
dist/test/
.npmignore

// npm pack
{YOUR_LIB}-*.tgz

Finally we have our Travis CI config.

In the before_script section, we want to:

  • Setup our project ( npm run setup )
  • Build our project ( npm run build )

In this sections, things are collapsed after being ran. This is a good place for setup. The script section is where you will want your actual testing:

  • Run tests ( npm run test-only )

Before we go to deploy our code, we want to build our deployment. Specifically we want a before_deploy section:

  • Build documentation ( npm run typedoc )

Finally we can deploy. TravisCI has many deployment providers. The most important to me are the GitHub Pages, npm and GitHub Releases providers.

The following .travis.yml is set up to deploy typedoc, as well as publish the zipped NPM package to GitHub releases.

language: nodejs

before_script:
- npm run setup
- npm run build

script:
- npm run test-only

after_success:
- npm run typedoc
- npm pack

deploy:
- provider: pages
skip_cleanup: true
local_dir: docs/
github_token: $GITHUB_TOKEN
on:
tags: true
- provider: releases
api_key: $GITHUB_TOKEN
file_glob: true
file: "{YOURLIB}-*.tgz"
skip_cleanup: true
on:
tags: true
- provider: npm
skip_cleanup: true
email: "youremail@example.com"
api_key: $NPM_TOKEN
on:
tags: true

To get your $GITHUB_TOKEN you will need to go to https://github.com/settings/tokens and enable repo access.

To get your $NPM_TOKEN you will need to run npm login and copy the key in your ~/.npmrc file.

The final workflow ends up such that pushes to master branch are built and tested, but new GitHub tags are deployed. Make sure to remember to keep that changelog up to date and increment your semver properly!

Finished Product:

For an idea of how all this integrates together take a look at my personal JS/TS util library rlib. It implements a similar workflow.