Merge branch 'pr-667' into devel

This commit is contained in:
Nick Martin
2013-02-12 12:37:05 -08:00
3 changed files with 132 additions and 41 deletions

View File

@@ -9,6 +9,17 @@
// /app [user code]
// /app.json: [data for server.js]
// - load [list of files to load, relative to root, presumably under /app]
// - manifest [list of resources in load order, each consists of an object]:
// {
// "path": relative path of file in the bundle, normalized to use forward slashes
// "where": "client" [could also be "server" in future]
// "type": "js", "css", or "static"
// "cacheable": (client) boolean, is it safe to ask the browser to cache this file
// "url": (client) relative url to download the resource, includes cache
// busting param if used
// "size": size in bytes
// "hash": sha1 hash of the contents
// }
// /dependencies.json: files to monitor for changes in development mode
// - extensions [list of extensions registered for user code, with dots]
// - packages [map from package name to list of paths relative to the package]
@@ -224,6 +235,9 @@ var Bundle = function () {
// of file as buffer.
self.files = {client: {}, client_cacheable: {}, server: {}};
// See description of manifest at the top
self.manifest = [];
// list of segments of additional HTML for <head>/<body>
self.head = [];
self.body = [];
@@ -329,6 +343,12 @@ _.extend(Bundle.prototype, {
return inst;
},
_hash: function (contents) {
var hash = crypto.createHash('sha1');
hash.update(contents);
return hash.digest('hex');
},
// Call to add a package to this bundle
// if 'where' is given, it's an array of "client" and/or "server"
// if 'from' is given, it's the PackageInstance that's doing the
@@ -377,25 +397,36 @@ _.extend(Bundle.prototype, {
minify: function () {
var self = this;
var addFile = function (type, finalCode) {
var contents = new Buffer(finalCode);
var hash = self._hash(contents);
var name = '/' + hash + '.' + type;
self.files.client_cacheable[name] = contents;
self.manifest.push({
path: 'static_cacheable' + name,
where: 'client',
type: type,
cacheable: true,
url: name,
size: contents.length,
hash: hash
});
};
/// Javascript
var codeParts = [];
_.each(self.js.client, function (js_path) {
codeParts.push(self.files.client[js_path].toString('utf8'));
delete self.files.client[js_path];
});
self.js.client = [];
var combinedCode = codeParts.join('\n;\n');
var finalCode = uglify.minify(
combinedCode, {fromString: true, compress: {drop_debugger: false}}).code;
var hash = crypto.createHash('sha1');
hash.update(finalCode);
var digest = hash.digest('hex');
var name = path.sep + digest + ".js";
self.files.client_cacheable[name] = new Buffer(finalCode);
self.js.client = [name];
addFile('js', finalCode);
/// CSS
var css_concat = "";
@@ -405,16 +436,21 @@ _.extend(Bundle.prototype, {
delete self.files.client[css_path];
});
self.css = [];
var final_css = cleanCSS.process(css_concat);
hash = crypto.createHash('sha1');
hash.update(final_css);
digest = hash.digest('hex');
name = path.sep + digest + ".css";
addFile('css', final_css);
},
self.files.client_cacheable[name] = new Buffer(final_css);
self.css = [name];
_clientUrlsFor: function (type) {
var self = this;
return _.pluck(
_.filter(self.manifest, function (resource) {
return resource.where === 'client' && resource.type === type;
}),
'url'
);
},
_generate_app_html: function () {
@@ -423,10 +459,10 @@ _.extend(Bundle.prototype, {
var template = fs.readFileSync(path.join(__dirname, "app.html.in"));
var f = require('handlebars').compile(template.toString());
return f({
scripts: self.js.client,
scripts: self._clientUrlsFor('js'),
head_extra: self.head.join('\n'),
body_extra: self.body.join('\n'),
stylesheets: self.css
stylesheets: self._clientUrlsFor('css')
});
},
@@ -495,12 +531,64 @@ _.extend(Bundle.prototype, {
if (is_app) {
if (fs.existsSync(path.join(project_dir, 'public'))) {
files.cp_r(path.join(project_dir, 'public'),
path.join(build_path, 'static'), {ignore: ignore_files});
var copied =
files.cp_r(path.join(project_dir, 'public'),
path.join(build_path, 'static'), {ignore: ignore_files});
_.each(copied, function (fs_relative_path) {
var filepath = path.join(build_path, 'static', fs_relative_path);
var normalized = fs_relative_path.split(path.sep).join('/');
self.manifest.push({
// path is normalized to use forward slashes, so deliberately
// not using path.sep here
path: 'static/' + normalized,
type: 'static',
where: 'client',
cacheable: false,
url: '/' + normalized,
size: fs.statSync(filepath).size,
hash: self._hash(fs.readFileSync(filepath))
});
});
}
dependencies_json.app.push('public');
}
// Add cache busting query param if needed, and
// add to manifest.
var processClientCode = function (type, file) {
var contents, url;
if (file in self.files.client_cacheable) {
contents = self.files.client_cacheable[file];
url = file;
}
else if (file in self.files.client) {
// Client css and js becomes cacheable with the addition of the
// cache busting query parameter.
contents = self.files.client[file];
delete self.files.client[file];
self.files.client_cacheable[file] = contents;
url = file + '?' + self._hash(contents)
}
else
throw new Error('unable to find file: ' + file);
self.manifest.push({
// path is normalized to use forward slashes
path: 'static_cacheable' + file.split(path.sep).join('/'),
where: 'client',
type: type,
cacheable: true,
url: url,
// contents is a Buffer and so correctly gives us the size in bytes
size: contents.length,
hash: self._hash(contents)
});
};
_.each(self.js.client, function (file) { processClientCode('js', file); });
_.each(self.css, function (file) { processClientCode('css', file); });
// -- Client code --
for (var rel_path in self.files.client) {
var full_path = path.join(build_path, 'static', rel_path);
@@ -515,25 +603,6 @@ _.extend(Bundle.prototype, {
fs.writeFileSync(full_path, self.files.client_cacheable[rel_path]);
}
// -- Add query params to client js and css --
// This busts through browser caches when files change.
var add_query_param = function (file) {
if (file in self.files.client_cacheable)
return file;
else if (file in self.files.client) {
var hash = crypto.createHash('sha1');
hash.update(self.files.client[file]);
var digest = hash.digest('hex');
return file + "?" + digest;
}
// er? file we don't know how to serve? thats not right...
return file;
};
self.js.client = _.map(self.js.client, add_query_param);
self.css = _.map(self.css, add_query_param);
// --- Server code and generated files ---
app_json.load = [];
files.mkdir_p(path.join(build_path, 'app'), 0755);
for (var rel_path in self.files.server) {
@@ -575,6 +644,8 @@ _.extend(Bundle.prototype, {
// --- Metadata ---
app_json.manifest = self.manifest;
dependencies_json.extensions = self._app_extensions();
dependencies_json.exclude = _.pluck(ignore_files, 'source');
dependencies_json.packages = {};
@@ -585,7 +656,7 @@ _.extend(Bundle.prototype, {
}
fs.writeFileSync(path.join(build_path, 'app.json'),
JSON.stringify(app_json));
JSON.stringify(app_json, null, 2));
fs.writeFileSync(path.join(build_path, 'dependencies.json'),
JSON.stringify(dependencies_json));

View File

@@ -294,9 +294,14 @@ var files = module.exports = {
// If options.ignore is present, it should be a list of regexps. Any
// file whose basename matches one of the regexps, before
// transformation, will be skipped.
//
// Returns the list of relative file paths copied to the
// destination, as filtered by ignore and transformed by
// transformer_filename.
cp_r: function (from, to, options) {
options = options || {};
files.mkdir_p(to, 0755);
var copied = [];
_.each(fs.readdirSync(from), function (f) {
if (_.any(options.ignore || [], function (pattern) {
return f.match(pattern);
@@ -306,8 +311,12 @@ var files = module.exports = {
if (options.transform_filename)
f = options.transform_filename(f);
var full_to = path.join(to, f);
if (fs.statSync(full_from).isDirectory())
files.cp_r(full_from, full_to, options);
if (fs.statSync(full_from).isDirectory()) {
var subdir_paths = files.cp_r(full_from, full_to, options);
copied = copied.concat(_.map(subdir_paths, function (subpath) {
return path.join(f, subpath);
}));
}
else {
if (!options.transform_contents) {
// XXX reads full file into memory.. lame.
@@ -317,8 +326,10 @@ var files = module.exports = {
contents = options.transform_contents(contents, f);
fs.writeFileSync(full_to, contents);
}
copied.push(f);
}
});
return copied;
},
// Make a temporary directory. Returns the path to the newly created

View File

@@ -88,10 +88,19 @@ var run = function () {
var info_raw =
fs.readFileSync(path.join(bundle_dir, 'app.json'), 'utf8');
var info = JSON.parse(info_raw);
var bundle = {manifest: info.manifest, root: bundle_dir};
// start up app
__meteor_bootstrap__ = {require: require, startup_hooks: [], app: app};
__meteor_bootstrap__ = {
require: require,
startup_hooks: [],
app: app,
bundle: bundle
};
__meteor_runtime_config__ = {};
Fiber(function () {
// (put in a fiber to let Meteor.db operations happen during loading)