Developing on Staxmanade

Porting a Large JSPM BabelJS project to Typescript

(Comments)

I've been working on a project for over a year now that was originally written in ES6 with (some async/await ES7) using BabelJS to transpile and really enjoying the experience of using the latest javascript.

ES6/7 was great at the beginning, when I had 1, then 2... then 5 files. The project was small, I could pretty much load the entire application into my own head at once, reason, tweak, plow forward.

It didn't take too long before the project grew beyond a single load into memory. Now I'm mentally paging out components from different files as I work on various sections of the application.

Just like anything, once the project grew to this point, it became a little more difficult to maintain. Now, I consider myself a fairly solid developer, and that's likely how I made it this far without a compiler as my components were small, surface areas tight and the interactions between them were well managed. I also had a decent number of in-app unit and integration tests because generally (but not always) I'm a test-first kind of guy.

However, that didn't stop me from breaking things, making mistakes or just out-right screwing up a javascript file here and there along the way.

While working on this project, it always niggled me that the project would keep growing without the ability for the most basic of unit-tests to run (the compiler). Almost a year ago I even remember trying to use Typescript but using it with JSPM and without having VisualStudio Code all together, it just never came together (or I just didn't try hard enough).

But this past week, I gave it another go, and while I'm not totally there (or where I'd like to end up), I'm quite happy with the results I've made so far and am impressed and quite happily working in a project that has completely been ported to Typescript from ES6/7 using BabelJS.

First some idea about the project.

Now, when it comes to large software projects, I'm pretty sure I shouldn't be calling this project a "large" as the subject of this post seems to label it... But for a system built only by me in some nights and weekends, it is the largest single app I've built alone, so that's where I'm defining "Large".

The project has just about a hundred javascript files/components/classes/modules and comes in just above 12,000 lines of code. That's not counting of course all the dependencies pulled in through JSPM (using both NPM and Github). In fact I really need to look at my dependencies and see where I can trim some fat, but that's not the subject of this post.

Porting from Babel to TypeScript

With some context about the project this is coming from out of the way, I thought it would be helpful to outline the steps (or stumbles) I took along the way to get my project up and running using TypeScript with JSPM.

Pre-migration steps

Below are steps I took to get this thing going. I doubt they're perfect or even apply to your or anyone elses projects, but here's hoping they're helpful

  1. In a fresh temp folder, I used the jspm init command to setup a fresh new project and selected the Typescript transpiler option.

this allowed me to inspect what a "fresh" project from JSPM would look like with Typescript setup.

  1. The next thing I did was review the angular getting started guide to see what Typescript specific configurations they used.

Now, my project isn't Angular (it's actually React based), but I thought I could learn a little something along the way. I don't know if I actually gleaned anything while doing this (as I'm writing this post a ways after I actually did the work, but as an FYI, you might learn something reading it)

What steps did I take to port the project?

Looking back at the series of commits during my port, here's basically what I did. In some cases order doesn't matter below, but I left this list in the order of my projects git commit log.

  1. Renaming each file with the .jsx extension to .tsx (Typescript's variant of JSX) (note: not renaming anything but code I wrote - so don't touch anything in jspm_packages or node_modules folders etc.
  2. jspm install ts <-- installing the Typescript jspm plugin
  3. Updated my jspm.config.js transpiler flag with the following:
-  transpiler: "plugin-babel",
+  transpiler: "Typescript",
+  TypescriptOptions: {
+    "tsconfig": true // indicates that a tsconfig exists that should be used
+  },

Then I updated my jspm.config.js's app section with the following.

   packages: {
-    "app": {
-      "defaultExtension": false,
-      "main": "bootstrap.jsx",
-       "meta": {
-        "*": {
-          "babelOptions": {
-            "plugins": [
-              "babel-plugin-transform-react-jsx",
-              "babel-plugin-transform-decorators-legacy"
-            ]
-          }
-        }
-      }
-    },
+    "app": { // all files within the app folder
+      "main": "bootstrap.tsx", // main file of the package (will be important later)
+      "format": "system", // module format
+      "defaultExtension": "ts", // default extension of all files
+      "meta": {
+        "*.ts": { // all ts files will be loaded with the ts loader
+          "loader": "ts"
+        },
+        "*.tsx": { // all ts files will be loaded with the ts loader
+          "loader": "ts"
+        },
+      }
+    },
  1. Created a tsconfig.json file
{
 "compilerOptions": {
    "target": "es5",                /* target of the compilation (es5) */
    "module": "system",             /* System.register([dependencies], function) (in JS)*/
    "moduleResolution": "node",     /* how module gets resolved (needed for Angular 2)*/
    "emitDecoratorMetadata": true,  /* needed for decorators */
    "experimentalDecorators": true, /* needed for decorators (@Injectable) */
    "noImplicitAny": false,         /* any has to be written explicitly*/
    "jsx": "react"
  },
  "exclude": [   /* since compiling these packages could take ages, we want to ignore them*/
    "jspm_packages",
    "node_modules"
  ],
  "compileOnSave": false        /* on default the compiler will create js files */
  1. Renamed *.js files to *.ts. (Similar to step 1 above with jsx -> tsx but now just the plain JavaScript files)
  2. In all of my source code where I used to do this: import foo from './foo.js' I removed the .js extensions like import foo from './foo'
  3. I did NOT remove the .jsx extension in my import statements - but renamed them to tsx so import foo from './foo.jsx' became import foo from './foo.tjs'
  4. Next I added a file at the root of my client project called globalTypes.d.ts, this is where I could hack in some type definitions that I use globally in the project.
  5. Then I started adding my type definitions...

I used the typings tool to search for TypeScript type definitions. And if I found one, I would typically try to install them from npm.

For example: searching for react like typings search react shows me that there is a DefinitelyTyped version of the type defs and I now know that we can use NPM to install them by typing npm install --save @types/react

So I installed a ton of external library typings.

  1. Next, started looking around my editor VisualStudio Code in hopes to see a bunch of typing errors reported, and was surprised to see very few. No, not because I'm so good at JavaScript that my TypeScript was perfect. Far from it... The problem I had was the tsconfig.json file was not at the root of my project (was at the root of my client site) - but it was nested several folders down from the root of my project. For some reason the editor wasn't picking it up until I opened the editor from the location the tsconfig.json file was rooted, things didn't work.

Honestly, I don't know what the above was about - but was something I ran into. I can't say for certain if it is still an issue - I think I'm starting to see editor features load up regardless of what folder I open things - so your mileage may vary.

  1. Once the TypeScript editor features started lighting up in VS Code, my next steps were start to take the TypeScript's feedback and implement either typing work-arounds or fixing actual bugs the compiler found.

THE END - ish

Where am I now?

The above steps were really all I went through to port this project over to TypeScript and it was relatively seamless. That's not to say it was simple or easy, but definitely do-able, and worth it.

It's been a few weeks since I ported the project to TypeScript and I'm really kicking myself for not doing it sooner.

The editor assist with intellisense of function calls from internal and external modules and their usage/function signatures saves time researching external documentation.

Other observations since the move.

  1. Builds seem to be a little faster with TypeScript than Babel. I can't say I can prove this. I didn't do any actual tests on this, but just a feeling I got after migration.
  2. Sourcemaps seem to actually work. Whenever I used BabelJS, debugging and stepping through async/await it just never seemed to line up right for me. This was likely user-error or in-proper configuration of babel on my part, so who knows... but having working source-maps is AMAZING, especially with the async/await feature.
  3. One area of concern that I haven't yet worked through. Is the JSPM typescript loading up in the browser - or running jspm bundle app at the command line doesn't report any typescript errors - or fail any builds. However, I'm glad it doesn't because something isn't quite rite with my configuration as every import of a .tsx file reports an error. So, for now I'm just relying on the red squigglies in my VS Code editor to help me catch typing errors.

If you go for this port in you're own project, I hope this was helpful, and that your port goes well.

Happy TypeScripting!

In App Unit test example with JSPM, React, WinJS and Mocha

(Comments)

A while back I wrote about In App Unit Tests and have been having too much fun creating little starter plunks on Plunker. So...

As a simple demonstration on in-app unit tests I've thrown together a little plunk that shows in-app tests within the a WinJS Pivot. And add to the layers of abstraction I'm using react-winjs which I'm LOVING over normal WinJS development.

Link to: JSPM/React/WinJS/Mocha Plunk

If you like this starter, I have it and a few more linked here: for re-use

I'd like to highlight this particular starter at bit more in this post, not only because there are a few more concepts to this basic plunk, but also because I'm fairly happy with the MochaJS React component that I've now copied around and re-used a few times in some small projects where use In App Unit Tests.

Plunker file overview

  • index.html - The index file is a very basic JSPM bootstrap page that loads the app.jsx react component.
  • app.jsx - Defines a WinJS Pivot control where we render the in-app MochaTests.jsx React component. This also defines the test file(s) and using MochaJS's global detection we can tell the react MochaTests component what test files to load and run as well as what globals are allowed to exist.
  • config.js - This is JSPM's config that defines what version of React we're using and to use babel for transpilation.
  • tests.js - is our Mocha set of unit tests. We can have multiple test files if we want, just have to define what files to load in app.jsx.

Lastly the MochaTests.jsx which I'll include the full component below:

easier to copy-paste inherit for myself in the future

import mocha from 'mocha';
import React from 'react';

export default class MochaTests extends React.Component {

    static get propTypes() {
        return {
            testScripts: React.PropTypes.array.isRequired,
            allowedGlobals: React.PropTypes.array
        };
    }

    constructor(props) {
        super(props);
    }

    componentDidMount() {

        var testScripts = this.props.testScripts;
        var runTests = this.runTests.bind(this);

        // for some reason importing mocha with JSPM and ES6 doesn't
        // place the mocha globals on the window object. The below
        // handles that for us - as well as setting up the rest of the
        // test scripts for the first run
        mocha.suite.on('pre-require', context => {
            var exports = window;

            exports.afterEach = context.afterEach || context.teardown;
            exports.after = context.after || context.suiteTeardown;
            exports.beforeEach = context.beforeEach || context.setup;
            exports.before = context.before || context.suiteSetup;
            exports.describe = context.describe || context.suite;
            exports.it = context.it || context.test;
            exports.setup = context.setup || context.beforeEach;
            exports.suiteSetup = context.suiteSetup || context.before;
            exports.suiteTeardown = context.suiteTeardown || context.after;
            exports.suite = context.suite || context.describe;
            exports.teardown = context.teardown || context.afterEach;
            exports.test = context.test || context.it;
            exports.run = context.run;

            // now use SystemJS to load all test files
            Promise
            .all(testScripts.map(function(testScript) {
                console.log("Adding Mocha Test File: ", testScript);
                return System.import(testScript);
            })).then(runTests, function(err) {
                console.error("Error loading test modules");
                console.error(err);
            });

        });
        mocha.setup('bdd');
    }

    runTests() {
        var allowedGlobals = this.props.allowedGlobals || [];

        this.refs.mocha.getDOMNode().innerHTML = "";

        mocha.checkLeaks();
        mocha.globals(allowedGlobals);
        mocha.run();

    }

    render() {
        return (
            <div>
                <style>{"\
                  #mocha-stats em { \
                      color: inherit; \
                  } \
                  #mocha-stats { \
                    position: inherit; \
                  }\
                  #mocha .test.fail pre { \
                      color: red; \
                  } \
                "}</style>

                <button onClick={this.runTests.bind(this)}>Rerun Tests</button>

                <div id="mocha" ref="mocha"></div>
            </div>
        );
    }
}

Usage example of this React MochaTests component.

// Define what test files get loaded by the MochaTests component
var testScripts = [
  './tests.js'
];


var allowedTestGlobals = [
  // Declare what globals are allowed to be created during any test runs.
];


// Usage of MochaTests in a react render() method.
<MochaTests testScripts={testScripts} allowedGlobals={allowedTestGlobals} />

I'm not expecting to see a large up-tick in WinJS apps out there with in-app unit tests that run in the browser, however hopefully the MochaTests.jsx React Component is of value to you and can be utilized outside of WinJS within almost any React app.

Please drop a line if you end up using it or if it can be adapted. If there's value in the component, maybe

Known Issue

If the number of tests starts to go beyond the height of the pivot in this sample, it has an issue where the WinJS Pivot cuts off at the bottom not allowing you to scroll and see the rest of the test output. I haven't dug into it yet because I've been clicking the failures: X link and it filters the U.I. to just the erroring tests.

If you happen to come up with a good solution, drop me a note - I'd love it. Thanks in advance!

Happy Testing!

JSMP/SystemJS Starter Plunker

(Comments)

I'm writing this post more for myself as a quick way to get going with JSPM, but if you find it useful please feel free.

Since I've recently been playing with JSPM, I've found it useful to kick-start my prototyping with a plunk. I've created a number of starter plunks below (and continue to add to the list.

Full list of JSPM starter plunks

JSPM Starter

JSPM, React

JSPM, MochaJS

JSPM, React, MochaJS

JSPM, React, MochaJS, (Sample jsx Component test)

JSPM, React, WinJS

JSPM, React, WinJS, MochaJS


How to use:

  1. Click one of the links above
  2. Review the various files. Open and edit the app.js file
  3. Start writing code!

Be sure to open your developer tools and monitor any console output for errors...

If you want to take more of a test driven approach to your prototype take a look at MochaJS with JSPM or go directly to the JSPM/MochaJS Plunk

Happy Prototyping!

Browser only MochaJS tests using SystemJS

(Comments)

I've been poking at SystemJS (you may have heard of it through JSPM) and one of the first things I like to setup when playing with a new JS framework is a way to run MochaJS unit tests which allow me to test-drive my prototypes and in this case the best part is we don't have to do any local command line installations or crazy gulp/grunt builds. We can right in the browser start writing plain ES6 and prototype using a library from npm or github.

SystemJS is a very exciting project with it's ability to import modules/code right from you're JS code itself. You can write you're JS using ES6 import syntax and in many cases SystemJS will magically import code via NPM or GitHub directly.

TL;DR

If you want to skip the details below and just see the plnkr.co go right ahead!

How to run our first Mocha test

First we need to get a simple web page setup. I'm going to use plnkr.co as it allows me to specify multiple files. This will allow me to more easily componentize my code for cleaner extraction into a project, gist or other...

Once you have a basic Plunker setup go ahead and delete everything except index.html of for now.

Now we're ready to start throwing our code in here... But before you do open you're browser's developer tools. I'm using Chrome on the Mac so Cmd+Option+j will do it. We need to be able to see the javascript console in case we see any errors with SystemJS loading of modules.

index.html <- paste the below in for you're Plunker index.html.

<!DOCTYPE html>
<html>

<head>
  <script src="https://jspm.io/system@0.18.js"></script>
  <script type="text/javascript">
    System.import('./testInit.js');
  </script>
</head>

<body>
</body>

</html>

With the above in the index.html you should see some errors printed to the console as SystemJS is trying to load ./testInit.js (but we haven't created it yet).

Before we create the testInit.js file let's first create a couple sample MochaJS test files that we want to test.

Here's our first test file: name it mochaTest1.js

Something cool about this test is once we get mocha wired up correctly, this test shows how seamlessly you can take a dependency on a 3rd party library like chaijs for help with assertions.

import { expect } from 'chai';

describe("This is a describe", function() {
  it("sample test that should pass", function() {
    expect(true).to.equal(true);
  });
  it("sample test that should fail", function() {
    expect(true).to.equal(false);
  });
});

Create another test file mochaTest2.js

import { expect } from 'chai';

describe("This is another describe", function() {
  it("sample test that should pass", function() {
    expect(true).to.equal(true);
  });
  it("sample test that should fail", function() {
    expect(true).to.equal(false);
  });
});

Creating two test files allows this sample to show how you can easily create and test multiple modules.

The meat and potatoes

Now is the juicy part on how to get Mocha to play nicely with this setup and run our tests.

Create a file and call it testInit.js (same as we named in our index.html and referenced it via System.import('./testInit.js')) and paste the below.

Feel free to read through it as I commented it thoroughly.

//
// This tells SystemJS to load the mocha library
// and allows us to interact with the library below.
//
import mocha from 'mocha';

//
// This defines the list of test files we want to load and run tests against.
//
var mochaTestScripts = [
  './mochaTest1.js',
  './mochaTest2.js'
];

//
// If you have a global or two that get exposed from your
// tests that is expected you can include them here
//
var allowedMochaGlobals = [
  'jQuery'
]


//
// Mocha needs a <div id="mocha"></div> for the browser
// test reporter to inject test results in to the U.I.
// Below just injects it at the bottom of the page. (You can get fancy here)
// Maybe you create a button in your website and allow anyone to run tests.
// Check out http://staxmanade.com/2015/03/in-app-unit-tests/ for more on the thought
//
var mochaDiv = document.createElement('div');
mochaDiv.id = "mocha";
document.body.appendChild(mochaDiv);

//
// Importing mocha with JSPM and ES6 doesn't expose the usual mocha globals.
// I found this is one way to manually expose the globals, however if you know of a better way please let me know...
//
mocha.suite.on('pre-require', function(context) {
  var exports = window;

  exports.afterEach = context.afterEach || context.teardown;
  exports.after = context.after || context.suiteTeardown;
  exports.beforeEach = context.beforeEach || context.setup;
  exports.before = context.before || context.suiteSetup;
  exports.describe = context.describe || context.suite;
  exports.it = context.it || context.test;
  exports.setup = context.setup || context.beforeEach;
  exports.suiteSetup = context.suiteSetup || context.before;
  exports.suiteTeardown = context.suiteTeardown || context.after;
  exports.suite = context.suite || context.describe;
  exports.teardown = context.teardown || context.afterEach;
  exports.test = context.test || context.it;
  exports.run = context.run;

  // now use SystemJS to load all test files
  Promise
    .all(mochaTestScripts.map(function(testScript) {
      return System.import(testScript);
    })).then(function() {
      mocha.checkLeaks();
      mocha.globals(allowedMochaGlobals);
      mocha.run();
    }, function(err) {
      console.error("Error loading test modules");
      console.error(err);
    });

});

mocha.setup('bdd');

Please let me know if you know of an easier way to get access to the mochas globals using SystemJS. The below works, but is a bit uncomfortable.

MochaJS Tests Right in the Browser...

How awesome is this. Couple bits of bootstrap code, and we can go author whatever we want right in the browser using ES6 (err EcmaScript 2015) and we're off and running.

warning NOT FOR PRODUCTION WORKFLOWS (yet)! warning

This approach is primarily for allowing quick prototyping. Don't implement a complete app like this and then expect any performance. SystemJS can potentially download a large number of dependencies and you should read up on JSPM production workflows.

Happy Browser-Only Testing.