How to Compile TypeScript into a Single File with SystemJS Modules with Gulp

Tue Mar 14 2017 09:47:22 GMT+0000 (Coordinated Universal Time)
  • blog
  • javascript
  • typescript
  • systemjs
  • modules
  • gulpjs
  • I've been messing around with TypeScript again for my game project and wanted a module loader to consume the single file produced by the TypeScript compiler. This time around I decided to use SystemJS and figured I'd share the lessons I learned along the way.

    Sample Project

    If you're interested in playing with the code, you can checkout this GitHub project I setup just for that reason.

    Previous Post

    I also posted about doing the same sort of thing with AMD and RequireJS complete with a GitHub sample project

    Project Breakdown

    Here's the gist of it. My project has the following requirements:

    1. Source code in TypeScript, organized in to multiple modules
    2. Load external modules into application as dependencies
    3. Transpile down to a single bundle file
    4. Load the bundle in the browser

    It seems pretty straight forward, right? Plus, because I'm using TypeScript I figured this would be easy peezy lemon-squeezy with the TypeScript compiler and rich documentation.

    As it turns out, it wasn't that simple.

    Wait. Where's GulpJS?

    It's in the sample project handling the transpiling the TypeScript through a task.

    It's actually not required, but rather a convienience for keeping all my build tasks together. I just put it in the title, because it matches the previous post.

    Problem 1: Using an External Module

    I wanted to use Moment.js to help handle date objects with my code.

    There were two parts to this:

    Using it in Development

    I use Visual Studio Code, which is a great TypeScript development environment.

    Normally, you would use the @types collection of defintion files from the NPM which is wired up by default. For Moment, we need to break that.

    The definition file for Moment is found in the library itself. Since I use NPM to handle all my dependencies, you just set this up in your tsconfig.json file.

    Then, in code, we import it.

    import moment from "moment";

    Remember: if your project is already using @types definition files, you'll need to add that folder to the typeRoots collection yourself.

    Bundling it Up

    Because we're using SystemJS, we need to do is configure it as a path to understand where to find the library when it gets referenced.

    In the sample project, we do it in script tag on the HTML page, but you can do this in wherever you end up doing your SystemJS configuration.

    SystemJS.config({
        "paths": {
            "moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"
        }
    });

    Problem 2: Loading the Bundle

    Making a bundle is easy. Consuming the bundle is something different.

    Making a Bundle

    If you're interested in bundling your code into a single file with the compiler, you're limited to AMD or SystemJS modules. This is configured in the tsconfig.json file included in the sample project with the module property. You can read more about it here in the TypeScript Handbook.

    Consuming the Bundle

    This is where I got stuck.

    Now I have this fancy bundle, but I need to figure out how to consume it in my HTML page. The solution is pretty simple, but it took some research and some tinkering, but I got there.

    Take a look at the <body> take of the HTML file:

    <body>
        <div id="display">
            <!-- script will display content here -->
        </div>
    
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.20.9/system.js"></script>
        <script src="bundle.js"></script>
        <script>
            SystemJS.config({
                "paths": {
                    "moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"
                }
            });
    
            SystemJS.import("game")
                .then((module)=> {
                    let g = new module.Game("display");
                    g.start();
                })
                .catch((error)=> {
                    console.error(error);
                });
        </script>
    </body>

    I blame myself for getting stuck considering this sort all documented well in the SystemJS documentation on GitHub. Either way, I had issues finding solid resources about using bundles. Hopefull this can help someone else in the future.

    Conclusion

    My problems can be traced back to my lack of experience with JavaScript module loaders. And yes, I know that ES6 Modules are coming, but the browsers are a ways away from having a full implementation (except for Safari).

    Until then, we'll be using TypeScript and Babel to help us get our modular JavaScript working in the browser.