Skip to content

Instantly share code, notes, and snippets.

@millermedeiros
Created September 3, 2011 16:20
Show Gist options
  • Save millermedeiros/1191420 to your computer and use it in GitHub Desktop.
Save millermedeiros/1191420 to your computer and use it in GitHub Desktop.
Example of how to use a single JS file for multiple pages of an application
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example</title>
<meta name="description" content="This is just an example">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
</head>
<body data-modules="foobar, lorem/ipsum">
<div id="wrapper">
Just an example
</div>
<script data-main="js/main.js" src="js/lib/require/require.js"></script>
</body>
</html>
// main.js is used only for settings and initializing application,
// all heavy logic is stored inside proper modules, it makes it
// easy to require core modules from inside the application and
// also keeps main.js small since settings adds too much noise
// to the real code.
//
// see: http://blog.millermedeiros.com/2011/05/single-entry-point-ftw/
// SETTINGS ========
require.config({
paths : {
'jquery' : 'lib/jquery/jquery'
}
});
// INIT APP ========
define(
[
// "require" as depencency so paths are relative to
// current context
'require',
'jquery',
'someOtherModuleUsedByAllPages'
],
function(require, $, someSharedModule){
function init(){
// if metadata on HTML grab it and do a require
// body have a `data-modules="foo, bar/ipsum, dolor"`
var modules = $('body').data('modules') || '';
if(modules){
require(modules.split(/\s*,\s*/), function(){
// do something when they finish loading, I usually
// make this kind of module to auto-instantiate,
// so we wouldn't need to do anything here
});
}
// depending on the project it may be better to simply try
// to match a className instead of adding each module to
// a data-attribute:
if( $('.my-awesome-calendar').length ){
require(['widgets/myAwesomeCalendar']);
}
someSharedModule.init();
}
// if you use URLs to find modules there is no need to wait
// for DOM-ready to start loading modules if you have too
// many paths it is better to create some sort of look-up
// table or use a routing system like crossroads.js to
// simplify the logic
switch(document.location.pathname){
case '/foo':
require(['sections/foo/main'], initSection);
break;
case '/foo/bar':
require(['sections/foo/main'], initSection);
break;
default:
//let's just assume we have a lot of pages with common features
require(['sections/simplePage'], initSection);
}
function initSection(section){
section.init();
}
//init app on domready
$(document).ready(init);
}
);
// This module is only used to decide which section should be loaded and
// initialized.
// This example is just to demonstrate how Crossroads.js can simplify
// the process of loading AMD modules on demand. It assumes it is a
// regular website but the same technique could be used for single page
// apps with very small teaks.
// ---
// Author: Miller Medeiros
// https://gist.github.com/1191420
define(['crossroads'], function(crossroads){
// ROUTES ====
var newsDetail = crossroads.addRoute('/news/{id}', loadSection);
newsDetail.rules = {
id : /^\d+$/, //should be numeric
//normalize value to return proper module path (which isn't an URL param)
normalize_ : function(request, params){
return [ 'news/article', params.id ];
}
};
var jobsDetail = crossroads.addRoute('/jobs/{id}/:title:');
jobsDetail.rules = {
id : /^\d+$/ //should be numeric
};
//we can also use the `SignalBinding.params` to set a default param (same
//effect as using `rules.normalize_`).
var jobsDetailBinding = jobsDetail.matched.add(loadSection);
jobsDetailBinding.params = ['jobs/detail'];
var basicSection = crossroads.addRoute('/{section}', loadSection);
basicSection.rules = {
section : ['news', 'jobs', 'home', 'contact'] //valid sections
};
// METHODS ====
function loadSection(path, rest_params){
var params = Array.prototype.slice.call(arguments, 1);
//I'm just assuming all sections modules are stored inside a folder
//called "sections" and that each section/sub-section have a "main.js"
//file.
//It's important to note that r.js won't inline these dependencies
//automatically since module names are generated dynamically, use the
//"includes" build setting or optimize each section individually.
require(['sections/'+ path +'/main'], function(mod){
mod.init.apply(mod, params);
});
}
//if it was a single page app we would probably create a public method to
//navigate between sections and also dispose previous section before
//initializing the new one, but that is outside the scope of this example.
// INIT ====
//parse current URL to decide what to do
crossroads.parse(document.location.pathname);
});
@millermedeiros
Copy link
Author

this gist was created as an answer to this thread on RequireJS mailing list and also to exemplify what I cover on my blog post about single entry points.

This example is very simplified but gives an idea of how flexible this approach is if compared to adding new script tags to the HTML and also how much you benefit from dynamically finding dependencies and keeping the dependency logic on the JS.

@cherian
Copy link

cherian commented Sep 21, 2011

Hi Miller,
Happened to stumble on this. Thanks for sharing.

The way I understand
Executing

node r.js -o app.build.js

will looking for modules and pick up the dependencies from the modules section which is of course in a js file app.build.js

modules: [
        //Just specifying a module name means that module will be converted into
        //a built file that contains all of its dependencies. If that module or any
        //of its dependencies includes i18n bundles, they may not be included in the
        //built file unless the locale: section is set above.
        {
            name: "foo/bar/bop",

            //For build profiles that contain more than one modules entry,
            //allow overrides for the properties that set for the whole build,
            //for example a different set of pragmas for this module.
            //The override's value is an object that can
            //contain any of the other build options in this file.
            override: {
                pragmas: {
                    fooExclude: true
                }
            }
        },

Now in your case how do I instruct node to hit the html and pick the modules listed in it? The ones under

switch(document.location.pathname){
            case '/foo':
                require(['sections/foo/main']);
                break;
            case '/foo/bar':
                require(['sections/foo/main', 'core/bar']);
                break;
            default:
                require(['sections/home']);
        }

I can only out this in an html file since the url is dynamically generated. Something like '{% url foo %} '

@millermedeiros
Copy link
Author

@cherian r.js can't parse HTML documents, you will need to list what modules should be included using the includes setting.

I've been using the approach listed above even for dynamically generated URLs, if I don't have control of the URL I just add some metadata to the <body> like <body data-modules="sections/foo/main, core/bar">.

If I know in advance how the URL structure will be and there is a lot of variations, I use crossroads.js to parse the URL and load proper module (just added a new example showing how to do it and how it can simplify the whole process). You have to note that since module names are generated dynamically ('sections/'+ path +'/main') they won't be inlined during the optimization, I usually optimize each section individually, so I only load main.js + section_file.

I would favor keeping all the JS modules loading behavior outside the HTML, it will give you more flexibility.

Cheers.

@lakshmivelakaturi
Copy link

I Miller,
For each module, the required path should be specified means like ,Account-->Account-->Creaeate accounts
capture

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment