Tuesday, February 24, 2015

Lightning Talk Presentations from the Recent AngularJS DC Meetup

Last week I participated in a series of lightning talks at the AngularJS DC Meetup, hosted by Difference Engine, and I thought I'd share the links to the slide decks and demos presented (unfortunately, the equipment recording the entire event failed, otherwise I would just share that).

Not all of the presenters have posted their material, but here what's been shared so far:

Monday, February 2, 2015

Using Grunt to Concatenate Only the JavaScript/CSS Files Used in Index.html

One of the most common uses of the Grunt task runner is to build a deployment package out of your development code for your website or web application, and part of that build process is usually a task that concatenates the CSS and JavaScript files into singular (or at least fewer) files for optimal download.

The grunt-contrib-concat Grunt plugin allows you to configure a concatenation task to target individual files or entire directories, like so:

concat: {
            js: {
                src: [ 'dev/jquery/jquery.js', 'dev/angular/services/*.js', 'dev/angular/directives/*.js' ],
                dest: '../build/combined.js',
                options: {
                    separator: ';'
                }
            },
        }

The only drawback is that you have to update the task's "src" property as you add or remove CSS and JavaScript assets from your web application.

As I was playing around with Grunt on a personal project, I came to wonder: could I create a Grunt task or set of tasks that could figure out which files to concatenate based on the <link> and <script> tags in my code?  Here's what I came up with.

My project is a single page web application powered by AngularJS.  It has an index.html file that serves as the "single page" that displays the appropriate view fragment (HTML files stored in a "views" directory) for a given page/route in the application.  None of those view fragments require additional CSS or JavaScript resources, so my index.html page pulls down all of the necessary CSS and JavaScript files.

In my index.html file, I wrapped my blocks of <link> and <script> tags with special HTML "build" comments, a set for my CSS block and a set for my JavaScript block:


<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My HTML Page</title>

    <!--build-css-start-->
    <link rel="stylesheet" media="screen" href="assets/app.css" />
    <link rel="stylesheet" media="screen" href="packages/bootstrap/dist/css/bootstrap.css" />
    <!--build-css-end-->

</head>
<body ng-app="demoApp">

    <!--Div where my Angular views will be displayed-->
    <div ng-view></div>

    <!--build-js-start-->
    <script type="text/javascript" src="packages/angular/angular.js" ></script>
    <script type="text/javascript" src="common/app.js" ></script>
    <script type="text/javascript" src="controllers/loginController.js" ></script>
    <script type="text/javascript" src="controllers/logoutController.js" ></script>
    <script type="text/javascript" src="services/authService.js" ></script>
    <script type="text/javascript" src="services/session.js" ></script>
    <!--build-js-end-->

</body>
</html>

I then created a Grunt task powered by the grunt-replace plugin that finds and parses those blocks via regex, extracting and modifying the file path within each "src" and "href" attribute and appending them to arrays stored as Grunt configuration properities. Each block is replaced by a single <link> and <script> tag pointed at the combined CSS and JavaScript file. The overall build task then executes the concat task, which concatenates all of the files in the respective configuration arrays into the combined files.

So when I run the master build task against my original set of development files:

...I end up with a build directory containing the index.html file, the views folders with the view HTML files, and a single CSS and JavaScript file: 

...and the index.html file itself looks like this:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My HTML Page</title>

    <link rel="stylesheet" media="screen" href="combined.css"/>

</head>
<body ng-app="demoApp">

    <!--Div where my Angular views will be displayed-->
    <div ng-view></div>

    <script type="text/javascript" src="combined.js"></script>

</body>
</html>

Cool, right?

If you want to try it out for yourself, the entire Gruntfile used to power the build task is below, with comments: