Using absolute paths with TypeScript, Babel and Browserify

How to import modules using non-relative paths in TypeScript and how to transpile the result to JavaScript with correct path resolution.

When using relative paths in a TypeScript project of a certain size and complexity the imports start to look like this:

import { SpecialCollection } from "../../special";
import { LoginComponent } from "../login";
import { TextUtils } from ".../../utils/text";
import { Router } from "../../../core/router";

It gets cumbersome to import modules this way and it gets harder to refactor.

The solution presented in this article can be summarized in the following:

  • Configure TypeScript to use non-relative paths.
  • Use TypeScript to transpile the code to ES6.
  • Configure Babel to transform the paths.
  • Use Babel to transpile the code to ES5.
  • Bundle the result for the browser using Browserify.

Setup custom paths in TypeScript

The TypeScript compiler can be configured with the following properties:

  • baseUrl: the base path for your modules.
  • paths: path mappings relative to baseUrl.

You can read more about these and other settings in Module Resolution.

Let's consider the following project structure:

src
│   main.ts
│ 
└───core
│   │   router.ts
│   
└───components
    │   special.ts
    │
    └───auth
    │   │   login.ts
    │
    └───utils
        │   text.ts

tsconfig.json could be configured like this:

{
    "files": [
        "src/main.ts"
    ],
    "compilerOptions": {
        "target": "es6",
        "baseUrl": "./src",
        "paths": {
            "@core/*": ["core/*"],
            "@components/*": ["components/*"],
            "@auth/*": ["components/auth/*"],
            "@utils/*": ["components/utils/*"]
        }
    }
}

Your imports could now be changed into the following:

import { SpecialCollection } from "@components/special";
import { LoginComponent } from "@auth/login";
import { TextUtils } from "@utils/text";
import { Router } from "@core/router";

Setup custom paths in Babel

We told TypeScript to transpile to ES6. We will use Babel to transform the paths and transpile to ES5.

To resolve the modules in Babel we will use babel-plugin-module-resolver.

Our .babelrc could look like this:

{
    "presets": ["env"],
    "plugins": [
        ["module-resolver", {
            "root": ["./src"],
            "alias": {
                "@core": "./src/core",
                "@components": "./src/components",
                "@auth": "./src/components/auth",
                "@utils": "./src/components/utils"
            }
        }]
    ]
}

root and alias are similar to baseUrl and paths from TypeScript but do notice the differences.

Build the final output

Now we can setup a gulp script do the compilation.

Create your project:

npm init

Install the necessary packages:

npm install --save-dev babel-plugin-module-resolver babel-preset-env babelify browserify gulp tsify typescript vinyl-source-stream watchify

An example gulpfile.js:

var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var tsify = require("tsify");
var watchify = require("watchify");
var babelify = require("babelify");

var watchedBrowserify =
    watchify(
        browserify({
            "basedir": ".",
            "entries": "src/main.ts"
            "debug": true,
            "cache": {},
            "packageCache": {}
        })
        .plugin(tsify)
        .transform(babelify, { "extensions": [".js", ".ts"] })
    );

function build() {
    return watchedBrowserify
        .bundle()
        .on('error', function(error){
            console.log(error.message);
            this.emit('end');
        })
        .pipe(source("bundle.js"))
        .pipe(gulp.dest("dist"));
}

gulp.task("build", [], build);
watchedBrowserify.on("update", build);
watchedBrowserify.on('log', function (msg) {
    console.log(msg);
});

tsconfig.json and .babelrc will be read automatically by the plugins.

In summary, the TypeScript source files will be transpiled using tsify, then transformed using babelify where the final paths will be set and finally bundled for the browser with Browserify.

Nuno Freitas
Posted by Nuno Freitas on October 14, 2017

Related articles