Files
meteor/tools/uniload.js
2014-10-02 21:29:39 -07:00

153 lines
5.4 KiB
JavaScript

var _ = require('underscore');
var bundler = require('./bundler.js');
var buildmessage = require('./buildmessage.js');
var release = require('./release.js');
var packageLoader = require("./package-loader.js");
var files = require('./files.js');
var catalog = require('./catalog.js');
// These are the only packages that may be directly loaded via this package. Add
// more to the list if you need to uniload more things! (You don't have to
// include the dependencies of the packages you directly load in this list.)
var ROOT_PACKAGES = [
'constraint-solver',
'dev-bundle-fetcher',
'ejson',
'js-analyze',
'ddp',
'logging',
'meteor',
'minifiers',
'minimongo',
'mongo',
'package-version-parser',
'boilerplate-generator',
'webapp-hashing',
'xmlbuilder'
];
// Load isopacks into the currently running node.js process. Use
// this to use isopacks (such as the DDP client) from command-line
// tools (such as 'meteor'). The requested packages will be loaded
// together will all of their dependencies, and each time you call
// this function you load another, distinct copy of all of the
// packages (except see note about caching below). The return value is
// an object that maps package name to package exports (that is, it is
// the Isopack object from inside the sandbox created for the newly
// loaded packages).
//
// Caching: There is a simple cache. If you call this function with
// exactly the same release and packages, we will attempt to return
// the memoized return value from the previous load (rather than
// creating a whole new copy of the packages in memory). The caching
// logic is not particularly sophisticated. For example, the cache
// will not be flushed if packages change on disk, even if it should
// be, but using a different release name will flush the cache
// completely.
//
// When run from a checkout, uniload only loads local (from the checkout)
// packages: never packages from troposphere. When run from a release build,
// uniload only loads pre-built isopacks that are distributed alongside the
// tool: never local packages or packages from troposphere (so in this mode, it
// never compiles the source of a real package).
//
// Options:
// - packages: The packages to load, as an array of strings. Each
// string may be either "packagename" or "packagename.slice".
//
// Example usage:
// var DDP = require('./uniload.js').load({
// packages: ['ddp'],
// release: release.current.name
// }).ddp.DDP;
// var reverse = DDP.connect('reverse.meteor.com');
// console.log(reverse.call('reverse', 'hello world'));
var cacheRelease = undefined;
var cache = {}; // map from package names (joined with ',') to return value
var load = function (options) {
options = options || {};
// Check the cache first
var cacheKey = (options.packages || []).join(',');
if (_.has(cache, cacheKey)) {
return cache[cacheKey];
}
var undeclaredPackages = _.difference(options.packages, ROOT_PACKAGES);
if (undeclaredPackages.length) {
throw new Error("attempt to uniload undeclared packages: " +
JSON.stringify(undeclaredPackages));
}
// Set up a minimal server-like environment (omitting the parts that
// are specific to the HTTP server). Kind of a hack. I suspect this
// will get refactored before too long. Note that
// __meteor_bootstrap__.require is no longer provided.
var env = {
__meteor_bootstrap__: { startupHooks: [] },
__meteor_runtime_config__: { meteorRelease: "UNILOAD" }
};
var ret;
var messages = buildmessage.capture({
title: "Loading isopack"
}, function () {
// Load the code. The uniloader does not call the constraint solver, unless
// it is running from checkout, in which case it will use the constraint
// solver to build its packages in the catalog.
var loader = new packageLoader.PackageLoader({
versions: null,
catalog: catalog.uniload,
constraintSolverOpts: { ignoreProjectDeps: true }
});
// Build the bundler image.
//
// Passing in dependency versions doesn't really make any sense here. We
// don't know the previous dependencies of this package, and, anyway, if we
// are running from checkout, they are all +local, and if we are running
// from release it is a bunch of isopacks. So, we don't pass in
// dependency versions.
var image = bundler.buildJsImage({
name: "load",
packageLoader: loader,
use: options.packages || [],
catalog: catalog.uniload,
ignoreProjectDeps: true
}).image;
ret = image.load(env);
// Run any user startup hooks.
while (env.__meteor_bootstrap__.startupHooks.length) {
var hook = env.__meteor_bootstrap__.startupHooks.shift();
hook();
}
// Setting this to null tells Meteor.startup to call hooks immediately.
env.__meteor_bootstrap__.startupHooks = null;
});
if (messages.hasMessages()) {
// XXX This error handling is not the best, but this should never
// happen in a built release. In the future, the command line
// tool will be a normal Meteor app and will be built ahead of
// time like any other app and this case will disappear.
process.stderr.write("Errors prevented isopack load:\n");
process.stderr.write(messages.formatMessages());
throw new Error("isopack load failed?");
}
// Save to cache
cache[cacheKey] = ret;
return ret;
};
var uniload = exports;
_.extend(exports, {
load: load,
ROOT_PACKAGES: ROOT_PACKAGES
});