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.