Back

TechnologyAug 20, 2014

Modern Web Development Best Practices Powered by Grunt.js Part 6: Script Tags

Fernando Berrios

I briefly hinted in the previous post that a single reference to a minified file is better than having each individual file referenced in the HTML. In this part of the series, I’ll expand on this and provide code examples and instructions.

THE ISSUE WITH <SCRIPT>

The primary reason a single reference to a minified file is better is that whenever we have a <script> tag in the page, the browser has to stop execution of rendering and other browser processes until it figures out what to do with that tag. If that <script> tag has a block of JS, the browser has to parse and execute it before continuing. Then if it has a reference to a file (e.g., <script src=”js/app.js”>), the script has to wait to download the file, parse, execute, and then it can continue. So when we pile up multiple <script> tag references for all our dependencies the wait time for the user increases. This can lead to the perception that the page is slow or unresponsive.

The only time the use of multiple in-line scripts is justified is when you’re developing. In this stage, you don’t really care if it takes a couple of extra seconds to load the page if it means you can work unhindered. Multiple in-line scripts show you explicitly which files are being loaded and in what order. They also reference the full source code so you can debug easier.

If you follow the basic configuration covered in part one, or even take the Source Maps approach detailed above, you will already be following this best practice. A possible downside to these practices is that we have been defining the source files and dependencies as arrays inside of the “Gruntfile.js” file in order to use them with other tasks. As your application grows it might become cumbersome to maintain a list of references in your build configuration file.

A way to work around this is to use another plugin that allows us to define our dependencies and source files inside our HTML files during development but maintain a single reference to a compiled file for production. With this approach there’s no funky back-end conditional logic necessary.

tutorial:

“git checkout usemin” “npm install”

The plugin we will be using for this is called “grunt-usemin.” It works by scanning a target file and parsing it to find source files referenced within it. Then it automatically configures a concatenation and minification task and replaces the source file references with a single reference. Let’s take a look at it step by step.

First we change the “index.html” to include the source files and dependencies and wrap them in specially crafted HTML comments that “usemin” will use to create a list of sources:

&lt;!-- build:js js/app.js --&gt; &lt;script src="bower_components/jquery/jquery.js"&gt;&lt;/script&gt; &lt;script src="bower_components/handlebars/handlebars.js"&gt;&lt;/script&gt; &lt;script src="bower_components/ember/ember.js"&gt;&lt;/script&gt; &lt;script src="bower_components/accounting/accounting.js"&gt;&lt;/script&gt; &lt;script src="bower_components/moment/moment.js"&gt;&lt;/script&gt; &lt;script src="js-src/views/login.js"&gt;&lt;/script&gt; &lt;script src="js-src/app.js"&gt;&lt;/script&gt; &lt;!-- endbuild --&gt;

Note the special “<!– build:js js/app.js –>” comment. This will tell the “usemin” task that everything under this comment will correspond to a single script tag referencing a “js/app.js” file. Next we configure the plugin by pointing it to our “index.html”:

  useminPrepare: {       html: 'index.html'     },     usemin: {       html: 'index.html'     }

And then we can simplify our custom task a bit:

 grunt.registerTask('default', [ 'jshint',   'compass:clean',   'compass:dev'   ]);   grunt.registerTask('build-prod', [   'jshint',   'compass:clean',   'compass:prod',   'useminPrepare',   'concat',   'uglify',   'usemin'   ]);

As you can see, we no longer require a concatenation or minification step in our “default” task (which we’ll use heavily during development) since all the scripts will be in-line. Our “build-prod” task does the heavy lifting for us. After running the “jshint” and “compass” tasks it will:

  1. Run the “useminPrepare” task. This will scan “index.html,” find the source files referenced there, configure a “concat” task, and then an “uglify” task.

  2. Run the “concat” task. This will take the files referenced in the “index.html” file and generate a single “js/app.js” file.

  3. Run the “uglify” task. This simply minifies the “js/app.js” created in the previous step.

  4. Run the “usemin” task. This will replace the block of multiple inline scripts with a single reference to “js/app.js.”

Overall, this gives us a very workable approach. All references are now inside the HTML file, your build file stays cleaner, and the development process stays streamlined. However, note that the “grunt-usemin” plugin is very young. Currently, it doesn’t offer a lot of complex configurations you might need if you have a more complex scenario.

If you have big applications with big codebases, you might also want to look at an alternative way of including lots of source files, since manually maintaining a list inside an HTML file can also become cumbersome. One recommended tool to achieve script loading is RequireJS, which will allow you to write very modular JavaScript and dynamically load your application’s script files at run time. A typical app using RequireJS will only need this one <script> tag in the source HTML:

&lt;script data-main="js/main" src="js/vendor/require.js"&gt;&lt;/script&gt;

The good news is that “grunt-usemin” also handles RequireJS references like the one above. Be warned that there is a bit of a learning curve to get started with RequireJS. Migrating an existing codebase to use its module syntax can be a lot of work, but for big applications it will definitely help out in the long run. For a good primer on RequireJS, check out this post. There is also a Grunt plugin made specifically for this situation.

In the rest of this series, we’ll go over some other advanced concepts like caching, CDNs, image optimization, and a few other tricks. We’ll also see how to use more Grunt.js features and plugins and how to implement them.  In the meantime, follow @CrederaMSFT on Twitter and Credera on LinkedIn for more great best practices.

To view the rest of the Modern Web Development Best Practices Powered by Grunt.js series click here.