Moving from ES5 to ES2015 in ASP.NET

Have an old(er) ASP.NET project where javascript files put all their functions and variables in the global namespace? Want to move to modern javascript (ES2015) with dynamic module loading?

If you don't know where to begin, this guide might be able to help you. I was facing the same issue in an ASP.NET project (MVC, but it should be possible for WebForms too).

It's actually quite simple. We'll go from this:

  • Browser loads a page
  • Browser loads all included javascript files
  • Browser calls logic that is in the global namespace

To this:

  • Browser loads a page
  • SystemJS loads transpiled modules
  • Call logic that is encapsulated in modules

This allows for easier dependency management and testing. So here goes.

Dynamic module loading with SystemJS

If necessary install Node with npm and then bower:

npm install bower -g

First, we'll need SystemJS to load our modules. We'll need this in our ASP.NET project, so go to that folder in a console and run bower init. Answer the questions and then install SystemJS:

bower install system.js --save

If you prefer to work with Bundles, create one now in your BundleConfig.cs:

bundles.Add(new ScriptBundle("~/bundles/systemjs").Include(
    "~/bower_components/system.js/dist/system.js"));

Now we want to make sure SystemJS is always loaded, so we'll add it to the _Layout.cshtml file:

@Scripts.Render("~/bundles/systemjs")
<script>
    // set our baseURL reference path
    SystemJS.config({
        baseURL: '@Url.Content("~/Scripts/transpiled")'
    });
</script>

Here, we've configured SystemJS to load modules from our /Scripts/transpiled folder. This is where we'll store our javascript files after we've transpiled them from ES2015 (or ES6, the "new" javascript) to ES5 (the "old" javascript we all love dearly).

Transpiling with Babel

We'll now set up our transpiling. We'll use Babel for this. It will take ES5 from a source folder, transpile it to ES2015 and store it in a destination folder.

First, put your ES5 in a source folder of your choice. It's interesting to put it separate from your other ES5 code, so you can move over to ES2016 in multiple steps, instead of having to move over all of your code at once. It's best to take a file that lives more or less in isolation (few dependencies or dependants).

Now, we'll install Babel. Go to the root of your solution in a console and install it:

npm install babel-cli --save-dev

Notice we're installing the CLI because we want to call it from our NPM script.

Also install the ES2015 preset:

npm install babel-preset-es2015 --save-dev

This preset includes everything necessary to transpile ES2015 to ES5. You can read more on presets on the Babel website.

Now, let's make an NPM script that runs Babel. Add this to your package.json:

"scripts": {
    "build": "babel My.Website/Scripts/modules -d My.Website/Scripts/transpiled"

}

It's the build line that will make Babel take everything from the My.Website/Scripts/modules directory and transpile it to the My.Website/Scripts/transpiled directory.

To let Babel know it should use our previously installed preset, add a .babelrc file to the root of the project and add:

{
    "presets": ["es2015"]
}

Visual Studio

To have Visual Studio run our NPM script when building, I would advise to install the NPM Task Runner extension. You could do this with Grunt or Gulp, but the industry seem to be trending towards NPM scripts (also here) and this extension will be integrated into the Task Runner Explorer anyway.

After having installed this extension, we can bind the build script from our package.json file to the "Before Build" binding in the Task Runner Explorer.

Finally, ES2015!

Now we can change our old ES5 file to ES2015 syntax. Here's a very simple example.

Old:

function MyViewModel() {
    this.name = 'John Doe';
}

New:

export function MyViewModel() {
    this.name = 'John Doe';
}

This alone is enough to be a first step. Of course, you'd want to make a real class out of it later.

If you were previously loading the file in a Bundle, best to remove it now. We'll no longer be including the file in ASP.NET Bundles. Rather, we'll have SystemJS load it on request:

SystemJS.import('myViewModel.js');

Also, be sure to include the destination folder of Babel in your ASP.NET project so ASP.NET knows it should also host/publish it.

Caveat

One thing to keep in mind is that the SystemJS.import call returns a Promise. So if you previously did this in your HTML:

<script>
    var vm = new MyViewModel();
</script>

It will no longer work because at that moment, your myViewModel.js file will not have been loaded yet. Instead, you'll have to work around it by doing this:

<script>
    SystemJS.import('myViewModel.js').then(function(module) {
        // module contains anything you exported in the file
        var vm = new module.MyViewModel();
    });
</script>

Conclusion

This shows a first step to gradually move from the old way of working with javascript to a newer, more structured technique. In short it comes down to

  • Running Babel to transpile
  • Loading SystemJS to load the transpiled modules

There is currently one drawback I see and that is the fact that you have to include your source files (ES2015) and your transpiled files (ES5) in your project. So you should remember to only change the source files and to include any new files when you add them.