Node.js Javascript Combining October 12, 2011
The practice of combining and minifying JS files is one that should be done at all times. There's two ways to do this, one involves creating the combined file manually. The other involves fun and less future work.
I write my JS into many different files, breaking down functionality, but the client doesn't need to see that.
On my web server, I have a server side tag library that allows me to do stuff like include other files (so I can break up development into components), run "foreach" statements over arrays, conditional processing, and other stuff. I've added to this arsenal with a "combine" tag.
It's used like this:
<jsn:combine>
/js/file1.js
/js/file2.js
/js/file3.js
</jsn:combine>
These files get combined and added to the /js/combine/ folder, and the script tag gets written out in the response. This checks each time if one of the js files has been updated since the combined javascript file has been written.
There would be way too much code to spit out to show how all of the tags and the server works, so I'll just show the code that does the combine
var combineRegex = /(.*?\.js)\n/g;
var rootdir = site.path + site.contentRoot;
var combineFolder = "/js/combine/";
var file = combineFolder + jsnContext.localScope + ".js";
var writeTag = function(jsnContext, file){
var scriptTag = "<script type=\"text/javascript\" src=\"" + file + "\"</script>";
jsnContext.write(scriptTag);
callback(jsnContext);
}
var combineStats = filesystem.fileStats(rootdir + file);
var writeFile = combineStats == null;
var altroot = null;
if (site.config.isMobile && site.main != null){
altroot = site.path + site.main.contentRoot;
}
var paths = [];
while ((match = combineRegex.exec(files)) != null){
var localPath = match[1].trim();
var fullPath = rootdir + localPath;
var origStats = filesystem.fileStats(fullPath);
var altStats = filesystem.fileStats(altroot+localPath);
// add paths and determine if we should overwrite / create the combined file
if (origStats != null){
paths.push(fullPath);
writeFile = writeFile || combineStats.mtime < origStats.mtime;
}
else if (altroot != null && altStats != null){
paths.push(altroot+localPath);
writeFile = writeFile || combineStats.mtime < altStats.mtime;
}
}
writeTag(jsnContext, file);
if (writeFile){
filesystem.ensureFolder(rootdir + combineFolder, function(success){
if (success){
filesystem.readFiles(paths, function(content){
filesystem.writeFile(rootdir+file, content, function(success){
});
});
}
});
}
Some other stuff going on... my server has built in mobile site support. You can specify "this is the mobile site for this full site" and "for this mobile site, this is the main site", both running independent but they know of each other. I can have the file /js/mobile-only.js combined on the mobile site, where that file exists in the mobile site's content root, and I can have the file /js/main-site-only.js combined on mobile, where that file only exists on the main site.
Some nice to haves would be to also minify the files on the fly.