How to Use Typescript with SystemJS

Now that Angular 2 is out and given the importance of Typescript in the Angular 2 ecosystem, it’s time to understand how to create a proper building system for our projects.

If you see any Angular 2 code example out there, you’re going to see an extensive use of the import statement to load modules into our code like in the example below:

import {Component} from 'angular2/core';

@Component({
    selector: 'my-app',
    template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }

This code example is extracted from the Angular 2 website. It doesn’t really matter what this code does, what matters is that in the very first line they are importing the class Component  to use it in the code. So the first question that comes to my mind is: can we run this code directly in our browsers without any building process? The answer is Yes, we can if we use SystemJS.

SystemJS

Lets start this example code by creating a folder for our test project that we are going to call systemjs.

$ mkdir systemjs
$ cd systemjs

Our next step is to create two simple files that we are going to call main.ts and person.ts.

export class Person {
  public name: string = 'David';
}

In person.ts we are just defining a simple class using the ES6 syntax that we are going to be using in main.ts.

import {Person} from './person.ts';

let person = new Person();
console.log(person.name);

As we said before, in order for our import statements to work, we are going to need SystemJS. We can obtain this library from npm, but before doing that, we need to create a package.json file with default values to create a list with all of our project’s dependencies.

$ npm init --force

Now, we are ready to download SystemJS.

$ npm install systemjs --save

Because SystemJS is going to handle all of the dependencies of our code, every time that one of our files import a class, a function, a variable etc., from another file, SystemJS is going to do a call to the server to get that dependency.

In our case, main.js is importing a class from person.js, so SystemJS before serving the file main.js is going to do a XHR call to the server to get person.js first. It’s now obvious that we are going to need a web server for our app, and in this case we are going to be using the npm package lite-server.

$ npm install lite-server --save

After installing the package, we can start the web server.

$ lite-server
> zsh: command not found: lite-server

What happened? Turns out that when trying to invoke the lite-server package like that, our system is trying to lookup the PATH variable where all of our executable files live but in our case, the lite-server package lives inside of our own node_modules folder which is not by default in the PATH. We can fix that by creating a “script” inside package.json, that way, node is going to add by default our node_modules folder in the PATH and it’s going to be able to find the appropriate package.

{
  "name": "systemjs",
  "version": "1.0.0",
  "description": "",
  "dependencies": {
    "lite-server": "^2.1.0",
    "systemjs": "^0.19.22",
  },
  "scripts": {
    "dev": "lite-server"
  },
  "author": "",
  "license": "ISC"
}

Now we can run the server with the command

$ npm run dev

Loading the Dependencies in the Browser

With our dependencies in place, let’s move on and create our index.html file.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>SystemJS</title>
  <script src="node_modules/systemjs/dist/system.js"></script>
  <script>
    System.config({
      transpiler: 'typescript'
    });
    System
      .import('main.ts')
      .then(null, console.error.bind(console));
  </script>
</head>
<body>
</body>
</html>

In line 6 we are loading the SystemJS in the browser. In lines 8-10 we are configuring SystemJS to use typescript as the transpiler for our code (the transpilation is going to be performed at runtime in the browser). Finally, in lines 11-13, we are importing the entry point of our application, in this case the file main.ts.

We are ready now so we can start the web server with the command npm run dev, open our browser and go to the address localhost:3000. If we open the browser’s console, we can see that something went wrong with SystemJS.

> SyntaxError: Unexpected token <(…)

Whats missing? We told SystemJS that we are going to use typescript as the transpiler for our code but we didn’t give the browser access to the transpiler to perform the job. To fix that, we need to install the typescript transpiler locally using npm.

$ npm install typescript --save

Then we need to go back to index.html and load the library.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>SystemJS</title>
  <script src="node_modules/systemjs/dist/system.js"></script>
  <script src="node_modules/typescript/lib/typescript.js"></script>
  <script>
    System.config({
      transpiler: 'typescript'
    });
    System
      .import('main.ts')
      .then(null, console.error.bind(console));
  </script>
</head>
<body>
</body>
</html>

At this point if we reload the browser, we are going to be able to see the string “David” in the browser’s console.

There’s one minor detail that we should fix. If we take a closer look at the file main.ts, we can see that we are defining the extension (.ts) of the dependency person.ts

import {Person} from './person.ts';

let person = new Person();
console.log(person.name);

When working in node, we usually don’t specify the extension of a module so to be consisten in the frontend, we should be doing that import like:

import {Person} from './person';

To accomplish that, we need to configure SystemJS to use the .ts extension as the default. If we see the SystemJS config API we can notice that such property exists but for some reason, we are now forced to put our typescript code inside another folder, it can’t be directly in the root folder. Although the reason to enforce that is not clear to me, having a separate folder for our “source” code is actually a good practice. So we can stop protesting against the SystemJS convention and go ahead and create a new folder called src, and move the files main.ts and person.ts to that folder.

$ mkdir src
$ mv main.ts src
$ mv person.ts src

Our now folder structure is as follow:

.
├── index.html
├── package.json
└── src
    ├── main.ts
    └── person.ts

We can now update our index.html file to define the default extension and load the main.ts file from it’s new location.

<!DOCTYPE html>
<html>
  <head>
    <title>SystemJS</title>
    <script src="node_modules/systemjs/dist/system.js"></script>
    <script src="node_modules/typescript/lib/typescript.js"></script>
    <script>
      System.config({
        transpiler: 'typescript',
        packages: {
          src: {
            defaultExtension: 'ts'
          }
        } 
      });
      System
        .import('src/main')
        .then(null, console.error.bind(console));
    </script>
  </head>
  <body>
  </body>
</html>

In lines 10-13 we are defining the default extensions of our files using the package property. Notice that the keys right under the package property should match any of our folders, in this case we are referencing our src folder.

On line 17 we are changing the location of our main file and deleting the explicit mention to the .ts extension. Similarly, we can modify our main.ts file to delete the extension of the dependency.

import {Person} from './person';

let person = new Person();
console.log(person.name);

If we go back to see our browser’s console we can see that everything is working as expected with a more concise import syntax.

 

34 thoughts on “How to Use Typescript with SystemJS”

    1. Thanks for the recommendation Sean. I’m actually trying to decide the best build system for an angular 2 project with typescript. Right now I’m exploring webpack as well to see if it’s potentially simpler to implement than gulp + systemjs. I will take a look at jspm for sure.

      1. I also noticed when I tried this out yesterday, that typescript.js is nearly 4 megabytes in size! That’s simply too large in my opinion. I know that tsc (typescript compiler) can output JS files compatible with the SystemJS module system. If the TS is pre-transpiled, I assume we wouldn’t need include typescript.js… do you know how to bootstrap such files? I’ve been trying this all day to no avail.

    1. Why bundling? Sole purpose of SystemJS is that it can read modules *as they are*.

      You can load CommonJS, ES6 and AMD.

      Inability of doing so spawned the need of bundlers into-one-massive-production file.

      1. If you have a large project then there could be hundreds of network calls to load all of the js files. This is unacceptable in production over possibly a mobile connection.
        If using http2 this is less of a problem as many small network requests are much cheaper.

  1. For someone learning TypeScript, angular2 and everything in between, I’ve been racking my brain around module loading. This article goes a long way to clear things up (at least with SystemJS).

    Thx

  2. Thanks! This was very useful. I’ve been reading ng-book 2 and found systemjs and modules very confusing

  3. This is very useful and appreciate the way it was put together. In continuation to this tutorial, I like to see generating ‘build’ for production using systemJS. Look forward to it.

    Thanks

  4. In a typical example (found on internet)

    System.config({
    packages: {
    app: {
    format: ‘register’,
    defaultExtension: ‘js’
    }
    }
    });

    The “app” folder is storing source files with extension “js”. Could you tell me about “register” property there

    1. To be honest with you I don’t know. Since I wrote this post I started using webpack instead of systemjs and never looked back.

    1. I don’t know exactly why is failing for you but I can tell you that my IDE of choice when working with these technologies is Visual Studio Code and it’s amazing.

  5. I must tell you that this one is really nice article…. I have just started anuglarjs2, I was looking how initial process is working and this article give me the proper answer.

  6. A nice concise article but I would be interested in how you then productionise this.
    I suppose all you need to do is remove the tsc script tag and change the default extension.
    Do you have a different index.html for this or modify the same one with some sort of build job?

  7. Very good article and well written. But this is now a year old, and we don’t really transpile Typescript in the browser anymore (even for development/testing). Maybe the author can look at updating the acticle? Cheers.

  8. Thanks for the article, very useful information!

    I figured you can define the package as “./” then you are not forced to use the “src” folder, as in the following example:

    System.config({
    transpiler: ‘typescript’,
    packages: {
    “./”: {
    defaultExtension: ‘ts’
    }
    }
    });
    System
    .import(‘./main’)
    .then(null, console.error.bind(console));

  9. This is a great article, it helped me understand well, thanks for the detailed explanation.

  10. Typescript is ok. SystemJS is not. The example doesn’t work with the versions 2.0.x of SystemJS. You need to stay with version 0.19.x, e.g.:

    npm install –save systemjs@0.19.47

    The example works fine with the most recent stable versions of the other packages.

    1. @somups if you really need SystemJS 2.0, then:

      – a transpiler plugin must be specified (https://github.com/systemjs/systemjs/releases/tag/0.20.0), e.g. plugin-typescript:

      npm install –save systemjs@0.20 plugin-typescript

      – Update index.html:

      SystemJS 2.0 and transpiling inside the browser
      http://node_modules/systemjs/dist/system.js

      System.config({
      warnings: true,
      map: {
      ‘typescript’: ‘node_modules/typescript/lib/typescript.js’,
      ‘ts’: ‘node_modules/plugin-typescript/lib/plugin.js’
      },
      transpiler: ‘ts’,
      packages: {
      ‘src’: {
      main: ‘main’,
      defaultExtension: ‘ts’
      }
      },
      meta: {
      ‘typescript’: {
      “exports”: “ts”
      }
      }
      });
      System
      .import(‘src’)
      .then(null, console.error.bind(console));

      That’s it. It’s working with lite-server (npm run dev) but even as … a static page (e.g. with FF: firefox index.html)

So, what do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.