As a web development firm we run into many challenges, one of those challenges includes how to best structure and optimize JavaScript. AMD loaders like RequireJS have become ubiquitous in the JavaScript world for this reason. It’s ability to perform dependency management and minification is well known and easy to get going with, but we faced a challenge when trying to minify the javascript into multiple files with only the code needed for those specific pages. In this article I’m going to cover generating a main script, holding general site JavaScript and libraries, as well generating a script for every SPA that may exist on a site to reduce the amount of JavaScript loaded on each particular page.
It all starts with the main.js file, where we keep all of our shims for our external libraries and set up any dependencies necessary for each external library.
require.config({
shim: {
underscore: {
exports: '_'
},
'backbone.babysitter': {
deps: ['backbone', 'underscore']
},
backbone: {
deps: ["underscore", "jquery"],
exports: "Backbone"
},
marionette: {
deps: ['jquery', 'underscore', 'backbone', 'backbone.babysitter'],
exports: ['Marionette']
},
'jquery.cycle': {
deps: ['jquery']
},
'jquery.ui': {
deps: ['jquery']
},
site: {
deps: ['jquery']
},
moment: {
deps: ['jquery']
},
'jquery.cookie': {
deps: ['jquery']
},
'jquery.confirmon': {
deps: ['jquery']
},
'jquery.bindfirst': {
deps: ['jquery']
},
'owl.carousel': {
deps: ['jquery']
}
},
baseUrl: '/js',
paths: {
jquery: 'libs/jquery/jquery-1.10.2',
underscore: 'libs/underscore/underscore',
backbone: 'libs/backbone/backbone',
'backbone.pager': 'libs/backbone/backbone.pager',
marionette: 'libs/backbone/backbone.marionette',
'backbone.wreqr': 'libs/backbone/backbone.wreqr',
'backbone.babysitter': 'libs/backbone/backbone.babysitter',
modernizr: 'libs/modernizr/modernizr.custom.27469',
domReady: 'libs/require/domReady',
moment: 'libs/moment/moment',
templates: 'templates',
collections: 'collections',
models: 'models',
views: 'views',
controllers: 'controllers',
routers: 'routers',
init: 'init',
'event.aggregator': 'eventAggregator',
util: 'util',
events: 'events,',
'jquery.cycle': 'plugins/jquery.cycle2.min',
'jquery.titlealert': 'plugins/jquery.titlealert',
'jquery.ui': 'libs/jquery/jquery-ui-1.10.3.custom',
'plugins': 'plugins/plugins',
selectivizr: 'libs/selectivizr/selectivizr-min',
'jRespond': 'libs/jRespond/jRespond',
respond: 'libs/respond/respond',
'text': 'libs/require/text',
'jquery.cookie': 'libs/jquery/jquery.cookie',
'carousel': 'plugins/carousel',
'brightcove': 'libs/brightcove/BrightcoveExperiences',
'videos': 'plugins/videos',
extensions: 'extensions',
placeholders: 'libs/placeholders/placeholders',
'jquery.confirmon': 'libs/jquery/jquery.confirmon',
'jquery.bindfirst': 'libs/jquery/jquery.bindfirst',
'owl.carousel': 'plugins/owl.carousel'
}
});
//this is where all of the magic happens
require(['site']);
The last line there is a call to a site.js file that will contain general behavior for the site (things like menus, link events ect..).
Next we need to set up a javascript file for each SPA on the site, here are two examples:
1. Search Page:
require(['main'], function(){
require(['init/searchInit']);
});
2. Home Page:
require(['main'], function(){
require(['init/homeInit']);
});
As you can see we are setting up the main file as a dependency. This is needed when we are debugging and not using the minified versions of the apps. In the specific init files we are requiring goes all of the logic needed for the individual SPAs. This will change based on what SPA framework you are using, but the important thing to keep in mind is that everything that will be minified into the Search or Home script will only be dependencies traced in all modules starting from the homeInit.js or searchInit.js files.
One question you may be asking yourself now is, how is that possible considering we are making a require call to the main.js file. When using require.js you can actually set up build parameters when minifying the scripts. Here is what the build.json file should look like:
({
baseUrl: '../',
preserveLicenseComments: "false",
dir: '../../Scripts',
findNestedDependencies: "true",
mainConfigFile: '../main.js',
modules: [
{
name: 'main',
include: ['main', 'requireLib']
},
{
name: 'apps/search',
include: ['apps/search'],
exclude: ["main"]
},
{
name: 'apps/home,
include: ['apps/home],
exclude: ["main"]
}
],
skipDirOptimize: true,
paths: {
requireLib: 'libs/require/require'
},
include: 'requireLib'
})
What we are telling the r.js minifier here is to create a minified script for the home, search and main apps, and for the home and search apps, to exclude the main app.
If you would like to test it out, here is the command to do so (you need to be in the same directory as the build file when executing this command): node r.js -o build.json.
Lastly, you will need to set up an environment variable to point to the correct scripts when debugging or when in production. When minifying, the r.js optimizer will mirror the directories in the js folder and output them to the folder specified with the dir parameter in the build.json file (in this case it is '../../Scripts').
Here is what we put just before the close of the body element in the layout page:
@if (Model.IsMinified)
{
<script type="text/javascript" src="@Model.ScriptPath"></script>
}
else
{
<script data-main="@Model.ScriptPath" src="/js/libs/require/require.js"></script>
}
@if (!string.IsNullOrEmpty(Model.SPAScriptPath))
{
<script type="text/javascript" src="@Model.SPAScriptPath"></script>
}
When the Model.IsMinified variable is true, the Model.ScriptPath changes from /js/main.js to /Scripts/main.js. The same goes for the separate SPA paths, where they change from /js/apps/search.js when unminified to /Scripts/apps/search.js when minified.
About the Author:
Sammi has a great mix of Content Management Experience and .NET development skills. With over 4 years of development experience concentrated on ASP.NET MVC, Sitecore, and Orchard CMS he brings a very valuable skillset to the team. Graduating from UMASS Boston with a degree in MSIS he continued on to develop and architect highly complex web applications for a variety of clients.