How to bundle many TypeScript files using SystemJS

|5 min read|
Teklog
Teklog


Introduction

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. You can install it from Node Package Manager (NPM) and tinker with its tsc executable. It accepts a handful of options which may be stored inside tsconfig.json configuration file.

SystemJS is a JavaScript module loader that allows using ES Modules in all browsers. You can get it either via CDN or from Node Package Manager.

You may be wondering why do we even talk about SystemJS and Typescript together? Wouldn't it suffice to use Typescript compiler alone? 

Unfortunately, Typescript compiler is not that almighty. While it can compile each file to a given target, it has no way to bundle several files into one.

Strangely enough, it looks like Typescript supports ES6 Modules syntax inside *.ts files, since you can write either export or export default. You can even use Babel-like imports and named imports, thanks to esModuleInterop option. But underneath, Typescript transforms all export and import instructions via the module code generation option.


module can be one of: None, CommonJS, AMD, System, UMD, ES6, ES2015, ESNext.


Node environment uses CommonJS modules and don't need to bundle files into one, so in this case, Typescript alone would suffice. But browsers face more challenges. They neither understand CommonJS syntax nor ES6 Modules syntax. Thus, Typescript must delegate the hard work of bundling to:

  • either build-time bundlers such as Webpack
  • or run-time module loaders such as SystemJS, AMD.

To facilitate bundling, Typescript provides two compiler options: module and outFile. The moment you specify outFile option, module option becomes limited to System or AMD.

TL;DR

In this tutorial, we will focus on the SystemJS, which is one of the run-time module loaders. If you can't wait to see the code, I've created a Github repository for you. Otherwise, let's start off our tutorial.

SystemJS configuration

Firstly, we are going to install SystemJS

yarn add systemjs

With that, create an index.html file with the following content

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Typescript with SystemJS 5.x</title>
  <script src="./node_modules/systemjs/dist/s.js"></script>
  <script src="./node_modules/systemjs/dist/extras/named-register.js"></script>
</head>
<body>
<script>
  System.import('./bundle.js')
    .then(() => System.import('systemjs/app'))
    .catch(console.error.bind(console))
</script>
</body>
</html>

As you see in the snippet above, in the head section we must include the s.js and named-register.js scripts.

  • You could also include system.js, but s.js is a lighter version.
  • The necessity to include named-register.js came after SystemJS reached its 2.0 version. You can read more about SystemJS 2.0 release here.

Having the base SystemJS configuration, let's move on to the minimal Typescript configuration.

Typescript configuration

In here, we are going to install Typescript globally

yarn global add typescript

Next, we must create tsconfig.json file inside our project, as shown below

{
  "compilerOptions": {
    "target": "es5",
    "module": "system",
    "outFile": "./bundle.js",
    "strict": true,
    "esModuleInterop": true,
    "inlineSourceMap": true,
    "inlineSources": true
  },
  "exclude": [
    "node_modules"
  ]
}

Notable parts from the snippet above are:

  • module must be set to system, to use the SystemJS run-time module loader
  • outFile can point to any file which will bundle all your *.ts files. In this tutorial, it's a bundle.js since SystemJS reads this file inside index.html.
  • target is es5, since we do not use any transpiler (e.g Babel). Code inside bundle.js is thus understandable by every browser.

After you run tsc compiler, the content of your bundle.js file should be similar to the snippet below.

// For readability, only crucial bits are displayed
System.register("systemjs/app", ["shared/Family"], function (exports_5, context_5) { /*...*/ });
System.register("shared/Family", ["shared/Child", "shared/Parent"], function (exports_4, context_4) { /*...*/ } });
System.register("shared/Parent", [], function (exports_3, context_3) { /*...*/ });

Bundle.js is already a big file which uses many System.register calls, combining all our .ts modules. This is exactly what SystemJS 5.x with Extra Named register needs.

Importantly, systemjs/app was marked as the root element for SystemJS import. Corresponding systemjs/app.ts script loads all other .ts files from a shared directory.

Project structure

With the proper configuration of Typescript and SystemJS, let's dive into the project structure.

Feel free to clone related repository https://github.com/tekmi/blogging-typescript-with-bundlers yourself.

The main file lives inside systemjs/app.ts which imports all the needed dependencies.

import Family, { Child, Parent } from "../shared/Family";

const child1 = new Child('Marc');
const parent1 = new Parent('Adam');
const parent2 = new Parent('Yanjiao');

const myFamily = new Family();
myFamily.addMember(child1);
myFamily.addMember(parent1);
myFamily.addMember(parent2);

myFamily.printNames();

If you look inside the package.json, you will see that systemjs is the main dependency. Next to it, there is lite-server dev dependency which is used to start the local server.

Once you cloned the repository, you can follow those steps to see the Typescript and SystemJS in practice.

cd systemjs
yarn install
yarn dev

The last command will open your browser. So the only thing left is actually checking console logs inside your browser Developer Tools.

Conclusion

Typescript is gaining momentum and bringing lots of good things for JavaScript developers. It doesn't try to be an all-at-once tool and it doesn't try to lock us inside its ecosystem. With its target and module options, it gives us lots of flexibility.

Typescript focus on one thing. It brings a powerful compiler that enhances JS with strong typings. Other parts, like bundling, are gracefully delegated to other, battle-tested tools from the frontend world.

In this tutorial, we have focused on SystemJS and Typescript, but there is much more to explore. In the upcoming tutorials, I will be looking into AMD, Gulp and Webpack modules integration.