Monthly Archives: November 2014

Using Handlebars.js templates as precompiled JS files

I’ve previously used Handlebars templates in projects, but only in the simple ways– i defined a <script> block as inline html templates, and used in my js code.

However, i have a project where i need all the code, including html templates, as js files.
Luckily Handlebars can do this, but we’ll need to set up the proper node-based build environment to do so.

  • node.js
  • gulp task runner
  • bower for flat package management
  • handlebars for templates

The templates will get “precompiled” by gulp, resulting in a pure js file to include in the html page. Then we’ll be able to code in HTML, but deploy as JS.

First i create a new empty ASP.NET Web project in Visual Studio. I’ll call it: HandlebarsTest. Note that almost none of this is Visual Studio-specific, so 95% is applicable to any other development environment.

Next, i will set up Gulp and Bower, similar to how i did it in my 2 prior posts:

I will create the gulpfile.js like so (we’ll add it it):

var gulp = require('gulp');

gulp.task('default', ['scripts'], function() {

});

gulp.task('scripts', function() {
});

Open the node command prompt, and change to the new directory

cd HandlebarsTest\HandlebarsTest
npm init
npm install -g gulp
npm install gulp --save-dev
npm install gulp-uglify --save-dev
npm install gulp-concat --save-dev
npm install gulp-wrap --save-dev
npm install gulp-declare --save-dev

I will create the .bowerrc file like so:

{
    "directory": "js/lib"
}

OK, now for some handlebars stuff. One thing to understand is we need to do handlebars stuff at build/compile time AND at runtime. That means:

  • the precompilation will be run by gulp during build time (install gulp-handlebars using npm), and
  • the web browser will execute the templates with the handlebars-runtime library (install to the project using Bower)
npm install gulp-handlebars --global
npm install gulp-handlebars --save-dev

Bower (client-side) packages

I will use Bower to install the client side libs: handlebars, jquery, etc. First, create the bower.json file.

bower init

Next, start installing!

bower install jquery
bower install handlebars

Those files get installed to /js/lib/* , per my .bowerrc file. Now we can reference them in scripts, or use them for js bundles.

HTML, Javascript, and Handlebars templates together.

My use-case is to:

  1. Have a static HTML page
  2. Include a script tag which loads a single JS file
  3. The single JS file will load/contain the libraries AND the main execution code
  4. the main execution code will render a DIV element which renders a Handlebars template with an object.

HTML page just includes a single JS , which will be built:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Handlebars Test</title>
    <script type="text/javascript">
    (function() {
        function async_load(){
            var cb = 'cb=' +(new Date).getTime();

            var rmist = document.createElement('script');
            rmist.type = 'text/javascript';
            rmist.async = true;
            rmist.src = '../js/dist/bundle.js?' + cb;
            var x = document.getElementsByTagName('script')[0];
            x.parentNode.insertBefore(rmist, x);
        }
        if (window.attachEvent)
            window.attachEvent('onload', async_load);
        else
            window.addEventListener('load', async_load, false);
    }());
    </script>
</head>
<body>

    <h1>Handlebars Test</h1>

    <p id="main-content">
        There will be a dynamic element added after this paragraph.
    </p>
    <p id="dynamic-content"></p>

</body>
</html>

Handlebars templates will be in /templates/*.hbs . Here’s an example, i’m calling /templates/hellotemplate.hbs:

<div class="hello" style="border: 1px solid red;">
    <h1>{{title}}</h1>
    <div class="body">
        Hello, {{name}}! I'm a template. 
    </div>
</div>

Javascript will be in the /js/app/app.js and the other libraries

Here, i’m taking direction from https://github.com/wycats/handlebars.js#precompiling-templates

gulp-handlebars handles the precompilation. We will run the ‘gulp’ build process to precompile hbs templates to js later.

The app.js code will need to render the precompiled template with the data object, and add to the DOM somehow (using jQuery in this case).

"use strict";
var data = { title: 'This Form', name: 'Joey' };
var html = MyApp.templates.hellotemplate(data);
// console.log(html);

$(document).ready(function () {
    $('#dynamic-content').html(html);
});

Precompiling the templates and using them

I will modify the gulpfile.js to add a task for template compilation. This is my final version:

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');

gulp.task('default', ['templates','scripts'], function () {

});

var handlebars = require('gulp-handlebars');
var wrap = require('gulp-wrap');
var declare = require('gulp-declare');
var concat = require('gulp-concat');

gulp.task('templates', function () {
    gulp.src('templates/*.hbs')
      .pipe(handlebars())
      .pipe(wrap('Handlebars.template(<%= contents %>)'))
      .pipe(declare({
          namespace: 'MyApp.templates',
          noRedeclare: true, // Avoid duplicate declarations
      }))
      .pipe(concat('templates.js'))
      .pipe(gulp.dest('js/dist'));
});

gulp.task('scripts', function () {
    return gulp.src(['js/lib/jquery/dist/jquery.js', 'js/lib/handlebars/handlebars.runtime.js', 'js/dist/templates.js', 'js/app/**/*.js'])
      .pipe(concat('bundle.js'))
      .pipe(uglify())
      .pipe(gulp.dest('js/dist/'));
});

The key section is the ‘templates’ task. Translating:

  • read all *.hbs templates
  • process thru handlebars() precomilation
  • setting the namespace MyApp.templates
  • output to a single JS file js/dist/templates.js

The scripts task combines all the JS files to one bundle.js. However, i had some trouble debugging the code, so i first ran the JS without a bundle. I changed the html to use traditional javascript references instead of the bundle.js:

<head>
    <title>Handlebars Test</title>
    <script src="../js/lib/jquery/dist/jquery.min.js"></script>
    <script src="../js/lib/handlebars/handlebars.runtime.min.js"></script>
    <script src="../js/dist/templates.js"></script>
    <script src="../js/app/app.js"></script>
</head>

The load order is important– libraries, then templates, then the main app code. After fixing bugs, i get the desired HTML output in page:

handlebars-test-html-nobundle

Note the multiple GET requests. But it functionally is working.

Run the bundled version

Now that the JS code runs the templates with jQuery OK, we can remove the multiple script references and switch to the single bundle.js script.

Don’t forget to execute the ‘gulp’ build again (on the command line or via visual studio). Looking at the gulp ‘script’ task, note the order of the bundling concatenation needs to be the same order as the <script> tag includes would be in the above example. Otherwise, things wlil get out of order.

gulp.task('scripts', function () {
    return gulp.src(['js/lib/jquery/dist/jquery.js', 'js/lib/handlebars/handlebars.runtime.js', 'js/dist/templates.js', 'js/app/**/*.js'])
      .pipe(concat('bundle.js'))
      .pipe(uglify())
      .pipe(gulp.dest('js/dist/'));
});

Run the ‘gulp’ task again to build, via the command line or via the VS Task Runner Explorer, or the post-build event (i haven’t yet learned to use ‘watch’ to auto-rebuild). Don’t forget to change the HTML back to load /bundle.js instead of the multiple JS files.

Running/reloading the page, we finally get the precompiled templates inserted into the html page, via the single bundle.js:

handlebars-test-html-bundle

This stuff gets a bit crazy! Why do this? I guess i want to compile, process, and optimize my javascript code.

The project code is here: https://github.com/nohea/Enehana.CodeSamples/tree/master/HandlebarsTest . Without the /node_modules directory, since the file paths are too long for Windows ZIP. To reconstitute the directory, cd to the project folder, and run:

cd HandlebarsTest\HandlebarsTest
npm update

That will reconstitute the packages from the internet, since all the dependencies are listed in the package.json file. It also allows you to easily exclude the node_modules directory from version control, and allow other developers to ‘npm update’ to populate their own directories.

Using Bower for JS package management instead of NPM

This is a follow up to my post: Learning Gulp with Visual Studio – the JavaScript Task Runner

In my last post, i did the following:

  • Created a web project
  • Installed Node.js
  • Installed Gulp for running JS build tasks
  • Installed more JS libraries using NPM (node package manager)
  • Created simple HTML+JS app w/jquery
  • Created gulp tasks to minify and bundle JS into main.js
  • run proof of concept
  • added gulp task to post-build event so it is run automatically

In this post, i will make some changes to the above, in that i will replace the JS package manager NPM with Bower.

Why? NPM works, but has some disadvantages vs. Bower. NPM packages all keep copies of their own dependencies. This can result in multiple versions of libraries, like JQuery, even different versions. Bower uses a ‘flat’ model, so only one version is installed at a time.

Well, what about just using NuGet? That is possible too, but NuGet is not good at updating dependent versions after initial installation. If you install different NuGet packages with the same dependency, the “last one wins”.

To be completly frank, i think it’s kind of crazy to have 2 different JS package managers in the same project. But Bower does solve the problem of having a single “flat” package dependencies. I found some hacks/workarounds to do it in NPM, but i just can’t see making the brain investment unless i do node all day every day.

Installing Bower

Open the Node Command Prompt.

We are going to install it using NPM, globally:

npm install -g bower

At that point, we can install libraries using bower, similar to how NPM works.

You also need to install msysgit , but we already installed it when installing Git Extensions.

Bower uses Git to install packages.

Configuring  / .bowerrc

Packages will install to the bower_components/ directory. If you want to change the defaults, create a .bowerrc directory and set it.

If you want to change that, create a file in your project named .bowerrc and enter the JSON to specify:

{
    "directory": "js/lib"
}

The packages will get installed there instead.

I found trouble creating the file with a leading dot in Windows Explorer or Visual Studio. However, using Notepad, you can save a file with a leading dot.

Installing Gulp to the project, as a development tool

First we create a package.json file for our project.

npm init

We are going to use the Gulp task build tool, but that is not a client-side library. It is used as a development build-time tool, so we are installing it using npm.

npm install gulp --save-dev

We also need to install the gulp-* plugins we need at build time:

npm install gulp-uglify --save-dev
npm install gulp-concat --save-dev

Those commands create entries in your package.json file, under the key devDependencies. That means they can be automatically installed when the project is built on another machine.

Installing client-side packages

The whole point of Bower is to use it for client-side packages like JQuery, Angular, etc.

First, in the node command prompt, we will initialize it by creating a bower.json file. You can do it manually, or do the init command, and fill out the questions.

Here is what i got:

{
  "name": "GulpBowerWebTest",
  "version": "0.0.1",
  "authors": [
    "raulg <raulg@xxxxx.yyy>"
  ],
  "description": "Test using Bower",
  "license": "Proprietary",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "js/lib",
    "test",
    "tests"
  ]
}

Now i’m going to install some libs, like jquery (specific version 1.9.x).

bower-install-jquery-commandprompt-2014-10-30

Note in Solution Explorer (w/”Show all Files” on) they installed to /js/lib/, as specified in our .bowerrc

bower-install-jquery-sol-expl-2014-10-30

Updating Gulpfile.js

I copied the static index.html and app.js files from my prior project. I’m using the same Gulpfile.js, with only some updates to the paths, since they are in different locations:

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');

gulp.task('default', ['scripts'], function() {

});

gulp.task('scripts', function() {
    return gulp.src(['js/lib/jquery/jquery.js', 'scripts/**/*.js'])
      .pipe(concat('main.js'))
      .pipe(uglify())
      .pipe(gulp.dest('js/dist/'));
});

I’m trying out using Task Runner Explorer to run gulp. After tweaking and testing, i’m going to try checking the After Build event. This could be an alternative to setting it in the project’s post-build event. I’m not sure if its better yet, since i’ll need to have it work when building on the TeamCity CI server using MSBuild. Some VS tooling are not supported in MSBuild.

bower-taskrunner-afterbuildevent-2014-10-30

Now when i run the app, it works the same. The differences are that jQuery is at 1.9 version, and it should be easier to manage the client-side JS libs we use, and keep versions up to date and consistent (using the bower update command). I think that means jquery 1.9.x gets the latest, but will not go to 2.x if specified in the bower.json file.

References:

 

Learning Gulp with Visual Studio – the JavaScript Task Runner

As I start looking into building more high-performance web apps, we are lead into the area of Javascript and CSS bundling and minification. I know my “old-school” Javascript coding, but in recent years, there’s been a huge movement in the JS community regarding the whole toolchain, so i’m jumping in here.

There is a Microsoft ASP.NET way to do bundling now, as well as the ServiceStack Bundler project, which uses node.js . However, that also has some dependency on ASP.NET MVC code.

Since most of the development in this area has been built in the JavaScript / HTML / CSS community, the most mature tools are there. So i’m going to do a documented test of the tools in use. In recent years, i’ve done most web development in Visual Studio w/C#, Javascript, HTML, CSS. But i do have a background in professional Perl web development (years ago), so i have a different perspective. I’m coming at the new front-end JS toolsets from a point of discovery, so this may be most useful if you are also new to it. Don’t treat this as a “how to do it the best way” article.

Grunt is a “JavaScript Task Runner”, which can be thought of as a build tool for JS code. It uses node.js for executing tasks.

Gulp is another JavaScript Task Runner. It is in the same role as Grunt, but works with a JS code function instead of a JSON config. Also, it uses Node Streams instead of temp files, and does not require a ‘plugin’ per library. I was going to write a Grunt how-to, but i changed my mind and will do Gulp.

We want some kind of task runner, since we need to:

  • read all the JS library files and ‘minify’ them to take up the least space possible
  • bundle the files into one JS file, to reduce the number of HTTP requests the client needs to make

Thus, static files will not work. The task runner need to run during design time, and probably build/deploy time as well.

Installing the Toolset

First to install the toolset on Windows, the FAQ has recommended:

  • Installing msysgit (which will be installed if you have my favorite Git Extensions installed)
  • Installing node for windows
  • using Command Prompt or Powershell as a command line (i use command prompt here)

Then we can figure out later how to make it easier to use in Visual Studio and MSBuild.

OK, i installed Node and npm, the Node.js package manager. Think of node of being it’s own platform with its own infrastructure, its own EXE, and its own set of installable packages. NPM is how you install packages.

Installing Grunt (via NPM)

According to the getting started page, we install grunt-cli via NPM.

Run the “Node.js command prompt” as Administrator by right-clicking it in the start menu. Note: this is NOT the green “Node.js” shortcut, which will not work. Then in the prompt, type:

npm install -g grunt-cli

You will see it download and install. But never mind that/skip it, cause i just changed my mind (Javascript fashion changes rapidly – just hang on for the ride, and just make sure you know what problem a tool does before you try to use it). I like the gulpfile code syntax better that the grunt json format, and i heard it builds faster too.

Installing Gulp (via NPM)

Now that i changed my mind, here’s how we can install Gulp via NPM: (from the Getting Started)

npm install --global gulp

learninggulp-npm-gulp

Seems to have installed some dependent libs i know nothing about. No prob.

This is a global install on your machine. It seems you will also have to install it per project using npm “devDependiencies” with the –save-dev flag. More on that later. Global installs are for command-line utilities. If you are using client-side libs, you install them or require() them in your project.

Creating a New Web Project using Gulp

You can do this with no IDE by creating an empty directory and start there. But since my team uses Visual Studio, i will create an empty ASP.NET web app and install there manually.

In VS 2013, Add New Projects, ASP.NET , name it, and select the “Empty” template. That will create the minimal project.

For kicks, add a static HTML file for testing later. Right-click the project, Add -> HTML page. Call it index.html.

Installing Gulp to the project

In the Node command prompt (doesn’t have to be Administrator mode), cd to your project directory (not the solution directory).

npm install gulp --save-dev

learninggulp-npm-gulp-save-dev

This will install the Node infrastructure to the project as a /node_modules/ directory.

I recommend clicking the “View All Files” button in Solution Explorer and also clicking the “Refresh” button.

learninggulp-solexp-showfiles

This will show you the files not tracked by VS, but in the directory.

Create the minimal gulpfile.js

Right-click the project, and add new text/js file called gulpfile.js . The minimal file will contain:

var gulp = require('gulp');

gulp.task('default', function() {
  // place code for your default task here
});

Now on the command line, you can run the default ‘gulp’ command, which will do nothing.

learninggulp-run-default-task

Installing other JS libraries for use (via NPM)

The point of using a JS build tool is including other JS libraries in your project, for use at build time or run time. So for the sake of proof-of-concept, i will install the uglify lib (for minification), concat to bundle all js scripts, and the JQuery lib (for use in our client-side scripts).

There is a special gulp-uglify plugin (a bunch of others too), so we install that in the same way with npm.

npm install gulp-uglify --save-dev

concat also has a gulp plugin:

npm install gulp-concat --save-dev

I will install the standard JQuery lib as well. Note i can use NPM to install it, or i could have it Microsoft-style and install using NuGet. The only difference would be the path to the *.js files in the project.

npm install jquery --save-dev

jQuery installs to: /node_modules/jquery/dist/jquery.js

Create a primitive “real” app

I’m going to create a /scripts/app.js file which does a simple jQuery DOM manipulation.

// my app
$(document).ready(function () {
    $('#message-area').html("jQuery changed this.");
});

Also, the index.html file will reference/run the app.js and jquery scripts.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Gulp Web Test</title>
    <script src="node_modules/jquery/dist/jquery.min.js"></script>
    <script src="scripts/app.js"></script>
</head>
<body>
    <h1>Gulp Web Test</h1>
    <div id="message-area">Static DIV content.</div>
</body>
</html>

When you execute this traditional, static version of the app, it will run as expected:

learninggulp-jquerychangedthis-static

Starting to put it together

Now we configure the gulp file to run our 3 tasks together:

  1. minify our JS scripts
  2. concat them into one JS file

We need to add this to the gulpfile.js:

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');

gulp.task('default', ['scripts'], function () {

});

gulp.task('scripts', function () {
    return gulp.src(['node_modules/jquery/dist/jquery.js', 'scripts/**/*.js'])
      .pipe(concat('main.js'))
      .pipe(gulp.dest('dist/js'))
      .pipe(uglify())
      .pipe(gulp.dest('dist/js'));
});

 

Note i’ve created a new ‘scripts’ task, which is added as a dependent task to the ‘default’ task. The ‘scripts’ task is using the jquery.js file and all the *.js files in the /scripts/ directory as sources. They all go thru concat(), output to main.js , in the dist/js/ directory. They then go thru uglify().

Next,we run the ‘gulp’ command on the node command line. After a couple back and forth errors and corrections, we get this:

learninggulp-gulp-cmd-final

In Solution Explorer, you can refresh and now see the /dist/js/main.js file which was created.

learninggulp-output-main-js

It should contain our custom js code as well as the whole jQuery.

Then we can update the HTML reference to the new output bundle.js file, and see if it runs the same way. Delete the script tags for jquery.js and app.js, and add a single one for main.js

<script src="dist/js/main.js"></script>

When you run the same index.html in the browser, you should get the same “jQuery changed this.” output, even though the only js file is ‘main.js’. The output main.js is only 83K. I’m sure it could get smaller if we use gzip, etc. But it proves the concept works. It should be very easy to add other JS modules as needed.

The downside is installing this stuff to the project added 2,000 files under /node_modules/, adding about 12MB.

Visual Studio and MSBuild Integration

I did find some info on how to run Gulp and Grunt from withing VS as a post-build command, and hopefully in MSBuild as well:

For Gulp, we can just add a post-build step in the project –

  • right-click the project -> Properties…
  • click “Build Events”…
  • to the “Post-build event command-line:” add the following:
cd $(ProjectDir)
gulp

That will run the ‘gulp’ command via VS when you ‘build’, instead of having to use the command line. Much more convenient. You can delete the main.js file, then ‘build’ again – it will regenerate. Reference: Running Grunt from Visual Studio post build event command line
http://stackoverflow.com/questions/17256934/running-grunt-from-visual-studio-post-build-event-command-line .

Possibly much more full-featured and useful is the “Task Runner Explorer” VSIX extension. This is basically “real” tooling support in VS. I haven’t tried it yet, but i expect to try it.

Code for this post can be found here: https://github.com/nohea/Enehana.CodeSamples/tree/master/GulpWebTest

Update: I installed the Task Runner Explorer per the article above. It does work to view/run targets in the Gulpfile.js, so you don’t have to run on the command line, or have to build to execute the tasks.

learninggulp-task-runner-explorer

Update 2: i have a follow-up post: Using Bower for JS package management instead of NPM

Resources