//Async. vs. sync.: This is a build script not a server, therefore I use
//synchronous calls whenever possible. Anyone deliberately using async.
//calls HERE needs to have a certain body part located on top of the
//spine examined. That is great for SERVERS (and GUIs) but does not
//belong HERE.
var path = require('path'),
fs = require('fs'),
YUI = require('yui').YUI,
Y = YUI(),
mods,
//step functions, defined below
step1, step2, step3;
//Three steps:
//
//First, iterate over all our source files and collect their individual
//dependency information.
//This information is than injected into the loader, so that it can
//calculate dependencies for *our* modules - by default it only has
//the necessary meta-information for YUI3 modules.
//
//The second step is to write the collected information as string into
//our YUI3 startup code, to add it to the YUI3 loader configuration
//object - IN THE GROUPS SECTION: if added to the global section the loader
//will try to load it from the location of the YUI3 modules, since we do
//not add path or fullpath info. We have group-configuration for how to
//load OUR modules, however, so the info for OUR modules should go in there.
//Now the loader running in our application is going to have
//this information available when loading (OUR) modules on demand.
//
//In the third step we iterate over a number of collections of modules,
//let the loader calculate all the dependent modules required (using
//our collected meta-information), and write a file containing the combined
//code for ALL the required modules.
//Each HTML page in our application then loads this combo-file on startup,
//so that the initial page load has all modules it requires right from the
//start. Modules required later, based on user actions, are loaded on
//demand.
//This method significantly reduces page startup time because no additional
//requests for javascript files beyond the one combo file are necessary.
/*
* STEP 1
*
* Collect the meta information from our application modules, and when
* done call the next step. This step only collects information, it does
* not do anything with it.
*/
step1 = function () {
//This is where we collect the module information (name and dependencies, see
//http://yuilibrary.com/yui/docs/yui/yui-loader-ext.html)
var modules = {},
//This script is in the top level directory of our application codebase,
//and the build directory.
dir = path.join(__dirname, 'build/'),
files, i, file, stats, module_code, o;
//Iterate over all minified files (which are directly) in the build-directory.
files = fs.readdirSync(dir);
for (i = 0; i < files.length; i++) {
//shortcut that includes the directory
file = dir + files[i];
stats = fs.statSync(file);
//Look only at those files loaded on-demand by our app, and only at one
//of either the debug or the minified versions.
if (stats.isFile() && file.search(/-min.js$/) !== -1 && file.search(/start-/) === -1 ) {
module_code = fs.readFileSync(file, 'utf8');
//Note that this function is executed immediately, giving us exactly
//the same as block scope.
//By executing the module code inside this function we have a
//temporary scope. This way we avoid tainting he outside scope,
//for example the YUI.add function, which we would otherwise
//overwrite for the entire application.
o = (function () {
var YUI = {}, t;
//Overwrite the YUI.add function to be able to simply run the module files
//to capture the module meta data. We are interested in the 1st and the 4th
//argument only, name and the "details".
YUI.add = function (name, body, version, details) {
return {
name: name,
details: details
};
};
//Execute the module. The only thing executed should be a YUI.add()
//function with 4 arguments, simply because YUI3 modules consist of
//nothing but this one function:
//Also see http://yuilibrary.com/yui/docs/api/classes/YUI.html
// YUI.add(
// "my-module", //module name
// function (Y) {
// //the actual module code
// ...
// },
// "1.0", //module version
// {
// requires: [
// "my-other-module"
// ]
// }
// );
t = eval(module_code);
//The outside only gets this object returned by our YUI.add function above.
//Of course, this assumes we don't get malicious code - this is NOT "security".
return t;
}());
//...which we can now collect to inject it into the loader in the next step.
//See page http://yuilibrary.com/yui/docs/yui/loader.html, configuration
//property "modules", for all options.
modules[o.name] = o.details;
} // if... (valid file)
} //for... (all files)
//return the collected module meta information
return modules;
};
/*
* STEP 2
*
* Add the collected meta information to our application file's YUI3
* configuration object.
*/
step2 = function (modules) {
var tmp;
//Our ant build script produces build/start-debug.js (from src/start.js), then calls
//this script, and then produces start-min.js from it.
tmp = fs.readFileSync(path.join(__dirname, 'build/start-debug.js'), 'utf8');
tmp = tmp.replace(/\/\/DO NOT TOUCH THIS LINE - MODULES INSERTED BY BUILD SCRIPT/, "modules: " + JSON.stringify(modules, null, 4) + ",");
fs.writeFileSync(path.join(__dirname, 'build/start-debug.js'), tmp, 'utf8');
};
/*
* STEP 3
*/
//left out from this code sample - same as node.js CLI example at
//http://yuilibrary.com/yui/docs/yui/loader-resolve.html
mods = step1();
//The next steps both require the module dependency information we just collected.
step2(mods);
//step3(mods);