Write future proof java-script – ES6 and Build Automation using gulpJS, jspm and systemJS

Role of JavaScript in a web application development has increased significantly. It has resulted in very complex APIs and frameworks. Wouldn’t it be nice if we can bring some of the concepts from c#/java world in a cleaner way, so that we have more readable java-script. Well ES6 (EcmaScript 6) bring us very close to that. If you examine the code below, that is how you will be writing java-script in few years:

class Animal {
constructor() {
}
speak(content) {
alert(content);
}
}
export class Cat extends Animal {
speak(content) {
super.speak('cat says - meow ' + content);
}
}
export class Dog extends Animal {
speak(content) {
super.speak('dog says - woof ' + content);
}
}

Above are the three classes, one base class called ‘Animal’ and two inheriting classes extending ‘Animal’ i.e. ‘Cat’ and ‘Dog’. Don’t they look very clean and easy to understand. If you notice, we are only exporting the Cat and Dog classes, because that is all we need to consume from outside.

Did you just wish, you could write like this today, I cant wait. Not only that, knowing whats coming, I don’t want to keep putting effort into something and then re-write it in few years. But the problem is, at the moment ES6 is not supported in all the browsers, so we will need to put some mechanism into play to convert it to the code that browsers will understand today i.e. ES5. So when in future ES6 is supported more widely, we can remove the intermediate steps for conversion with no effect to our actual code base. There are two popular libraries, which transpile ES6 java-script to ES5 i.e. Traceur and BabelJS

Both supports, compile on demand at client side, but that adds an extra cost to the client side. I would pre-compile it on the server side to do it once instead of paying penalty every time at client side. If you are working on a web application, there could be other things you might want to do to your java-script and css files (such as minify, saas/less pre-compile etc) before delivering it to the client side.

1) Pre-compile JS/CSS
2) Referenced Package management
3) Dependency Loader

Pre-compile or pre-process is required when you use one of the transpiler such as less/saas or coffee-script/type-script etc. Babel is another type of transpiler which enable you to use ES6 or even some of ES7 features today without worrying about browser support. That is what we will explore today.

I wanted to have a nuget type package management of the java-script libraries and JSPM siting on top of npm gives me exactly that.

We will use Gulp within Visual Studio, empowered by thee JSPM commands to orchestrate pre-compilation and other ceremony required to prepare our JavaScript modules.

Eventually we will use SystemJS to load our modules and dependencies.

As a prerequisite we need nodeJS, GIT and Visual Studio installed on the system. We will first install JSPM, which is going to be the foundation of the entire process.

install jspm

Parameter ‘-g’ will instruct it to be installed globally, which I would recommend to do. Sometimes it does not add the JSPM to the path, to do that add the following path to PATH environment variables.

C:\Users\<user name>\AppData\Roaming\npm

We got everything we need installed on the system. Lets create a ASP.NET application.

create project

Select empty template:

create project 2

So the project structure will look like:

project structure

Now, it is time to add some JavaScript fun. We will start with JSPM configuration, to do that we need command prompt again. First take the command prompt to the root of your project, for me it is “d:\projects\es6fun\src\es6fun”.

jspm init

and choose the following options:

Package.json file does not exist, create it? [yes]:yes

Would you like jspm to prefix the jspm package.json properties under jspm? [yes]:yes

Enter server baseURL (public folder path) [./]:wwwroot

Enter jspm packages folder [wwwroot\jspm_packages]:

Enter config file path [wwwroot\config.js]:

Configuration file wwwroot\config.js doesn’t exist, create it? [yes]:yes

Enter client baseURL (public folder URL) [/]:

Which ES6 transpiler would you like to use, Traceur or Babel? [traceur]:Babel

This will create a package.json at the root and config.js and jspm_packages folder under wwwroot. You can change your options later on by editing config.js. JSPM will use package.json to keep track of the installed modules including dependencies at development time.

Now we need gulp to orchestrate JavaScript build tasks. The code we are going to write will have dependency on the gulp package, so lets reference that. To do that, we need add a entry into package.json (created by JSPM init). Add following at the top of the package.json:

{
"name": "es6fun",
"version": "0.0.0",

"devDependencies": {
"gulp": "^3.9.0",
"gulp-babel": "^5.1.0",
"gulp-changed": "^1.2.1",
"gulp-less": "^3.0",
"gulp-load-plugins": "0.10.0",
"gulp-plumber": "^1.0.1",
"gulp-print": "^1.1.0",
"gulp-sourcemaps": "^1.5",
"gulp-util": "^3.0.6",
"gulp-watch": "^4.3.4"
}

Once you save it, VisualStudio will get busy to download the recently referenced dependencies i.e. gulp* packages.

Lets write some script to start gulp magic. Gulp provides a mechanism to add the required tasks for execution.

var gulp = require("gulp");
var exec = require('child_process').exec;
var $ = require("gulp-load-plugins")({ lazy: true });

gulp.task('build:jspm', function (cb) {
exec("jspm install", function (err, stdout, stderr) {
console.log(stdout);
console.error(stderr);
cb(err);
});
});

We require few packages to support the code, first one is “gulp”, which is quite obvious. Then child_process is required to run npm commands. Third and last one is to lazy load the other required packages on need basis. So when we use package called plumber, we do not need to specify plumber package in the required statement. Above code adds a task to run a ‘jspm install’ command to refresh/install referenced packages.

This was just a bootstrap to do empower us to plugin the transpiler for ES6. In gulp we pipe the resources for processing. I broadly divide them into two categories, dependencies and application code. The main difference is the source and destination location in my project.

Dependencies will reside in /wwwroot/jspm_packages
I will put my code in /wwwroot/app

For pre-processing we will write a common function “compileJS” which will accept a pipe as parameter and return it back after adding some tasks. Lets have a look at the code below:

function compileJS(pipe) {
return pipe
.pipe($.plumber())
.pipe($.print())
.pipe($.sourcemaps.init())
.pipe($.babel({
modules: 'system',
moduleIds: false,
comments: false,
compact: false,
stage: 1,
optional: [
"es7.decorators",
"es7.classProperties"
]
}))
.pipe($.sourcemaps.write({ includeContent: false, sourceRoot: '/wwwroot' }))
.pipe(gulp.dest('wwwroot/lib/'));
}

The first plugin I am adding is the plumber (by lazy loading), which ensures that plugins would not break the pipe with their errors. I like visibility on what is happening so ‘print’ will spit out the name of the files currently under process as they go through the pipe. Processed java-script may not be as readable (transpiled, minified etc) for everyone’s liking, so let sourcemap help us with mapping the processed file back to the original one. It will come very handy while debugging.

Using the above function, we will pipe the processing of the dependencies and the client file.

gulp.task('build:js', function () {
compileJS(gulp.src(["wwwroot/jspm_packages/**/*.js", "!wwwroot/jspm_packages/npm/babel-core@*/**/*.js", "!wwwroot/jspm_packages/system.js", "!wwwroot/jspm_packages/es6-module-loader.js"], { base: 'wwwroot/' }));
gulp.src(["wwwroot/jspm_packages/system.js", "wwwroot/jspm_packages/es6-module-loader.js"], { base: 'wwwroot' }).pipe($.print()).pipe(gulp.dest('wwwroot/lib/'));

});

gulp.task('build:app-js', function () {
compileJS(gulp.src(["wwwroot/app/**/*.js"], { base: 'wwwroot/' }));
});

We will also need to compile the html and css files, in this example I am just going to do html for example sake:

gulp.task('build:html', function () {
return compileHTML(gulp.src(["wwwroot/jspm_packages/**/*.html"], { base: 'wwwroot/' }));
});

gulp.task('build:app-html', function () {
return compileHTML(gulp.src(["wwwroot/app/**/*.html"], { base: 'wwwroot/' }).pipe($.changed('wwwroot/lib/')))
});

function compileHTML(pipe) {
pipe.pipe($.changed('wwwroot/lib/'))
.pipe($.plumber())
.pipe($.print())
.pipe(gulp.dest('wwwroot/lib/'));
}

Almost there!! above gulp file will transpile our java-script code and put it under the ‘/wwwroot/lib’ folder. Lets try to use it in a sample html page. We will use systemJS as our module loader, which will do in way ES6 would do, we just need to add the config to tell our expected java-script paths.

<body>
To speak:<input type="text" id="content" name="content" /><br/>
<button onclick="dogSpeak()">Dog </button>
<button onclick="catSpeak()">Cat</button>
<script src="../jspm_packages/system.js"></script>
<script>
System.config({
"paths": {
"*": "*.js"
}
});
</script>
<script>
var lib
System.import('lib').then(function (_lib) {
lib = _lib;
});

function dogSpeak() {
var dog = new lib.Dog();
dog.speak(content.value);
}

function catSpeak() {
var cat = new lib.Cat();
cat.speak(content.value);
}
</script>
</body>

Place this html along with your java-script file under ‘wwwroot/app’ folder. Now trigger gulp to pre-process your html and java-script files. As instructed in the above gulp code, it will put the processed files under ‘wwwroot/lib/’ folder. Just load the html file from the processed folder in the browser and see the magic.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s