Using tslib for TypeScript 2.1 with Aurelia to save bandwidth

The TypeScript release notes for version 2.1 contains an interesting section named 'Support for external helpers library (tslib)'. This new feature allows you to use require.js to get the polyfills or helper functions that are normally generated by TypeScript in each file to support backward compatibility (using ESnext features supported by TypeScript, but not yet by all browsers).

Support for external helpers library (tslib) TypeScript injects a handful of helper functions such as _extends for inheritance, _assign for spread operator in object literals and JSX elements, and __awaiter for async functions.

Previously there were two options:

inject helpers in every file that needs them, or no helpers at all with --noEmitHelpers.

The two options left more to be desired; bundling the helpers in every file was a pain point for customers trying to keep their package size small. And not including helpers, meant customers had to maintain their own helper's library.

TypeScript 2.1 allows for including these files in your project once in a separate module, and the compiler will emit imports to them as needed.

What's the difference?

If we take a very basic class written in Aurelia with some ES features that are currently not supported in all browsers:

import { bindable } from 'aurelia-framework';

export class App {  
  @bindable()
  public message = 'Hello World!';
}

TypeScript will generate some helper methods (to make sure it works everywhere) in the outputted JavaScript (which will be repeated for every class inside your bundle, because TypeScript isn't aware of your bundling system):

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {  
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {  
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};

above the code of the actual class in the ES5 amd format you transpiled to:

define('app',["require", "exports", "aurelia-framework"], function (require, exports, aurelia_framework_1) {  
    "use strict";
    var App = (function () {
        function App() {
            this.message = 'Hello World!';
        }
        return App;
    }());
    __decorate([
        aurelia_framework_1.bindable(),
        __metadata("design:type", Object)
    ], App.prototype, "message", void 0);
    exports.App = App;
});

When the tslib option is enabled it will transpile to the following code:

define('app',["require", "exports", "tslib", "aurelia-framework"], function (require, exports, tslib_1, aurelia_framework_1) {  
    "use strict";
    var App = (function () {
        function App() {
            this.message = 'Hello World!';
        }
        return App;
    }());
    tslib_1.__decorate([
        aurelia_framework_1.bindable(),
        tslib_1.__metadata("design:type", Object)
    ], App.prototype, "message", void 0);
    exports.App = App;
});

As you can probably imagine, this will result in a lot less JavaScript code when you have a decent sized application (because it only needs to transfer(have) the helper/ polyfill once in the tslib package).

See it in action

We will add this to a new Aurelia TypeScript project generated by the Aurelia-CLI.

Aurelia TypeScript project

First, create a default Aurelia project with the Aurelia-CLI by using the command:

au new  

And use the option Default TypeScript to generate the default configuration for a TypeScript project.

Adding the tslib dependency

We can install the tslib dependency by performing the following command:

npm i tslib -S  

Setup tslib

Next, we will modify the compiler options, in the tsconfig.json file to enable this feature to work. Add the following option:

{
  "compilerOptions": {
    "importHelpers": true,
     ....
  }
}

Open the aurelia.json file, located in the aurelia_project folder, and add the dependency "tslib" on top.

"name": "vendor-bundle.js",
"prepend": [
  "node_modules/bluebird/js/browser/bluebird.core.js",
  "node_modules/requirejs/require.js"
],
"dependencies": [
  "tslib",
  "aurelia-binding",

Once you build your project now, it will automatically generate the require imports instead of the helper/ polyfill code.

Enjoy!