Files
meteor/packages/dynamic-import/client.js
Ben Newman bfc79eea9b Add //# sourceURL=<module id> comment to dynamic modules.
Fixes #8719 by displaying a recognizable (and even clickable) file name in
stack traces for errors thrown by dynamic code.
2017-05-29 21:29:22 -04:00

159 lines
4.3 KiB
JavaScript

var Module = module.constructor;
var cache = require("./cache.js");
// Call module.dynamicImport(id) to fetch a module and any/all of its
// dependencies that have not already been fetched, and evaluate them as
// soon as they arrive. This runtime API makes it very easy to implement
// ECMAScript dynamic import(...) syntax.
Module.prototype.dynamicImport = function (id) {
var module = this;
return module.prefetch(id).then(function () {
return getNamespace(module, id);
});
};
// Called by Module.prototype.prefetch if there are any missing dynamic
// modules that need to be fetched.
meteorInstall.fetch = function (ids) {
var tree = Object.create(null);
var versions = Object.create(null);
var dynamicVersions = require("./dynamic-versions.js");
var missing;
Object.keys(ids).forEach(function (id) {
var version = getFromTree(dynamicVersions, id);
if (version) {
versions[id] = version;
} else {
addToTree(missing = missing || Object.create(null), id, 1);
}
});
return cache.checkMany(versions).then(function (sources) {
Object.keys(sources).forEach(function (id) {
var source = sources[id];
if (source) {
var info = ids[id];
addToTree(tree, id, makeModuleFunction(id, source, info.options));
} else {
addToTree(missing = missing || Object.create(null), id, 1);
}
});
return missing && fetchMissing(missing).then(function (results) {
var versionsAndSourcesById = Object.create(null);
var flatResults = flattenModuleTree(results);
Object.keys(flatResults).forEach(function (id) {
var source = flatResults[id];
var info = ids[id];
addToTree(tree, id, makeModuleFunction(id, source, info.options));
var version = getFromTree(dynamicVersions, id);
if (version) {
versionsAndSourcesById[id] = {
version: version,
source: source
};
}
});
cache.setMany(versionsAndSourcesById);
});
}).then(function () {
return tree;
});
};
function flattenModuleTree(tree) {
var parts = [""];
var result = Object.create(null);
function walk(t) {
if (t && typeof t === "object") {
Object.keys(t).forEach(function (key) {
parts.push(key);
walk(t[key]);
parts.pop();
});
} else if (typeof t === "string") {
result[parts.join("/")] = t;
}
}
walk(tree);
return result;
}
function makeModuleFunction(id, source, options) {
// By calling (options && options.eval || eval) in a wrapper function,
// we delay the cost of parsing and evaluating the module code until the
// module is first imported.
return function () {
// If an options.eval function was provided in the second argument to
// meteorInstall when this bundle was first installed, use that
// function to parse and evaluate the dynamic module code in the scope
// of the package. Otherwise fall back to indirect (global) eval.
return (options && options.eval || eval)(
// Wrap the function(require,exports,module){...} expression in
// parentheses to force it to be parsed as an expression.
"(" + source + ")\n//# sourceURL=" + id
).apply(this, arguments);
};
}
function fetchMissing(missingTree) {
// Update lastFetchMissingPromise immediately, without waiting for
// the results to be delivered.
return new Promise(function (resolve, reject) {
Meteor.call(
"__dynamicImport",
missingTree,
function (error, resultsTree) {
error ? reject(error) : resolve(resultsTree);
}
);
});
}
function getFromTree(tree, id) {
id.split("/").every(function (part) {
return ! part || (tree = tree[part]);
});
return tree;
}
function addToTree(tree, id, value) {
var parts = id.split("/");
var lastIndex = parts.length - 1;
parts.forEach(function (part, i) {
if (part) {
tree = tree[part] = tree[part] ||
(i < lastIndex ? Object.create(null) : value);
}
});
}
function getNamespace(module, id) {
var namespace;
module.watch(module.require(id), {
"*": function (ns) {
namespace = ns;
}
});
// This helps with Babel interop, since we're not just returning the
// module.exports object.
Object.defineProperty(namespace, "__esModule", {
value: true,
enumerable: false
});
return namespace;
}