Skip to content

Commit

Permalink
Add automated HTML5 cache manifest generation. Disabled by default.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Robinson committed Apr 8, 2010
1 parent aabb1fa commit 9f50f08
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 3 deletions.
104 changes: 104 additions & 0 deletions Objective-J/CommonJS/lib/objective-j/cache-manifest.js
@@ -0,0 +1,104 @@

var FILE = require("file");
var MD5 = require("md5");

var FileList = require("jake").FileList,
var BundleTask = require("objective-j/jake/bundletask").BundleTask;

exports.generateManifest = function(productPath, indexFilePath)
{
indexFilePath = indexFilePath || FILE.join(productPath, "index.html");

if (!FILE.isFile(indexFilePath)) {
print("Warning: Skipping cache manifest generation, no index file at "+indexFilePath);
return;
}

var index = FILE.read(indexFilePath, { charset : "UTF-8" });

var manifestName = "app.manifest";
var manifestPath = FILE.join(productPath, manifestName);
var manifestAttribute = 'manifest="'+manifestName+'"';

print("Generating cache manifest: " + manifestPath);

var manifestOut = FILE.open(manifestPath, "w", { charset : "UTF-8" });
manifestOut.print("CACHE MANIFEST");
manifestOut.print("");
manifestOut.print("CACHE:");

var list = new FileList(FILE.join(productPath, "**", "*"));
list.exclude(manifestPath);
list.exclude("**/.DS_Store", "**/.htaccess");
list.exclude("**/LICENSE");
list.exclude("**/MHTML*");
list.exclude("**/CommonJS.environment/*");

// FIXME: bleh. heuristic for whether index file includes debug frameworks
if (index.indexOf('"Frameworks/Debug"') < 0)
list.exclude("**/Frameworks/Debug/*");

list.forEach(function(path) {
if (FILE.isFile(path)) {
// FIXME: check the actual sprited images file
if (BundleTask.isSpritable(path))
return;

// include hash of each file in comments to expire when any file changes
var hash = MD5.hash(FILE.read(path, "b")).decodeToString("base16");
manifestOut.print("# " + hash);
manifestOut.print(FILE.relative(productPath, path));
}
});

manifestOut.print("");
manifestOut.print("NETWORK:");
manifestOut.print("*");
manifestOut.close();

// Insert "manifest" attribute in <html> tag of index file
var matchTag = index.match(/<html[^>]*>/i);
if (matchTag) {
var htmlTag = matchTag[0];
var newHTMLTag = null;

var matchAttr = htmlTag.match(/manifest\s*=\s*"([^"]*)"/i);
if (matchAttr) {
if (matchAttr[1] !== manifestName) {
newHTMLTag = htmlTag.replace(matchAttr[0], manifestAttribute);
}
} else {
newHTMLTag = htmlTag.replace(/>$/, " "+manifestAttribute+">");
}

if (newHTMLTag) {
print("Replacing html tag: \n " + htmlTag + "\nwith:\n " + newHTMLTag);
var newIndex = index.replace(htmlTag, newHTMLTag);
if (newIndex === index) {
print("Warning: No change!");
} else {
FILE.write(indexFilePath, newIndex, { charset : "UTF-8" });
}
}
}
else {
print("Warning: Couldn't find <html> tag in "+indexFilePath);
}

// Add Content-Type "text/cache-manifest" for manifest file to .htaccess
// This allows manifests to work out of the box on Apache (if htaccess overrides are allowed)
var htaccessPath = FILE.join(productPath, ".htaccess");
var htaccess = FILE.isFile(htaccessPath) ? FILE.read(htaccessPath, { charset : "UTF-8" }) : "";

var htaccessOut = FILE.open(htaccessPath, "w", { charset : "UTF-8" });
htaccessOut.print(htaccess);

var openTag = "<Files "+manifestName+">";
if (htaccess.indexOf(openTag) < 0) {
htaccessOut.print("");
htaccessOut.print(openTag);
htaccessOut.print("\tHeader set Content-Type text/cache-manifest");
htaccessOut.print("</Files>");
}
htaccessOut.close();
}
32 changes: 31 additions & 1 deletion Objective-J/CommonJS/lib/objective-j/jake/applicationtask.js
Expand Up @@ -17,6 +17,8 @@ function ApplicationTask(aName)
this._frameworksPath = "Frameworks";
else
this._frameworksPath = null;

this._shouldGenerateCacheManifest = false;
}

ApplicationTask.__proto__ = BundleTask;
Expand All @@ -33,6 +35,7 @@ ApplicationTask.prototype.defineTasks = function()

this.defineFrameworksTask();
this.defineIndexFileTask();
this.defineCacheManifestTask();
}

ApplicationTask.prototype.setIndexFilePath = function(aFilePath)
Expand All @@ -59,6 +62,16 @@ ApplicationTask.prototype.frameworksPath = function()
return this._frameworksPath;
}

ApplicationTask.prototype.setShouldGenerateCacheManifest = function(shouldGenerateCacheManifest)
{
this._shouldGenerateCacheManifest = shouldGenerateCacheManifest;
}

ApplicationTask.prototype.shouldGenerateCacheManifest = function()
{
return this._shouldGenerateCacheManifest;
}

ApplicationTask.prototype.defineFrameworksTask = function()
{
// FIXME: platform requires...
Expand All @@ -72,7 +85,7 @@ ApplicationTask.prototype.defineFrameworksTask = function()
Jake.fileCreate(newFrameworks, function()
{
if (thisTask._frameworksPath === "capp")
OS.system("capp gen -f --force " + buildPath);
OS.system(["capp", "gen", "-f", "--force", buildPath]);
else if (thisTask._frameworksPath)
{
if (FILE.exists(newFrameworks))
Expand Down Expand Up @@ -106,6 +119,23 @@ ApplicationTask.prototype.defineIndexFileTask = function()
this.enhance([buildIndexFilePath]);
}

ApplicationTask.prototype.defineCacheManifestTask = function()
{
if (!this.shouldGenerateCacheManifest())
return;

var productPath = FILE.join(this.buildProductPath(), "");
var indexFilePath = this.buildIndexFilePath();

// TODO: can we conditionally generate based on outdated files?
var manifestPath = FILE.join(productPath, "app.manifest");
Jake.task(manifestPath, function() {
require("../cache-manifest").generateManifest(productPath, indexFilePath);
});

this.enhance([manifestPath]);
}

exports.ApplicationTask = ApplicationTask;

exports.app = function(aName, aFunction)
Expand Down
8 changes: 6 additions & 2 deletions Objective-J/CommonJS/lib/objective-j/jake/bundletask.js
Expand Up @@ -451,10 +451,14 @@ BundleTask.prototype.resourcesPath = function()
return FILE.join(this.buildProductPath(), "Resources", "");
}

// Don't sprite images larger than 32KB, IE 8 doesn't like it.
BundleTask.isSpritable = function(aResourcePath) {
return isImage(aResourcePath) && FILE.size(aResourcePath) < 32768;
}

BundleTask.prototype.defineResourceTask = function(aResourcePath, aDestinationPath)
{
// Don't sprite images larger than 32KB, IE 8 doesn't like it.
if (this.spritesResources() && isImage(aResourcePath) && FILE.size(aResourcePath) < 32768)
if (this.spritesResources() && BundleTask.isSpritable(aResourcePath))
{
this.environments().forEach(function(/*Environment*/ anEnvironment)
{
Expand Down

0 comments on commit 9f50f08

Please sign in to comment.