First attempt at runtime for loading packages with TLA

This commit is contained in:
zodern
2022-11-17 22:53:46 -06:00
parent c921ab59f8
commit 93d0bc5e34
2 changed files with 179 additions and 23 deletions

View File

@@ -1,9 +1,28 @@
function PackageRegistry() {
this._promiseInfoMap = Object.create(null);
this._packageQueue = [];
this._running = false;
}
var PRp = PackageRegistry.prototype;
var ASYNC_MAIN_MODULE = {};
// If potentialPromise is a promise, calls callback with the resolved value
// Otherwise, synchronously calls the callback with the value
PRp._waitForModule = function (potentialPromise, callback) {
if (
isThenable(potentialPromise) &&
potentialPromise.asyncMainModule === ASYNC_MAIN_MODULE
) {
potentialPromise.then((results) => {
callback(results);
});
} else {
callback(potentialPromise);
}
}
// Set global.Package[name] = pkg || {}. If additional arguments are
// supplied, their keys will be copied into pkg if not already present.
// This method is defined on the prototype of global.Package so that it
@@ -28,6 +47,12 @@ PRp._define = function definePackage(name, pkg) {
info.resolve(pkg);
}
// if (this._packageQueue.length > 0) {
// this._packageQueue.unshift().load();
// } else {
// this._running = false;
// }
return pkg;
};
@@ -60,6 +85,95 @@ PRp._promise = function promise(name) {
return info.promise;
};
// On the server, load is run immediately
// If it has async modules that are eagerly evaluated, it will return a
// promise that resolves after the package has been fully loaded.
PRp.load = function (name, deps, load) {
// console.log('start load', name);
var self = this;
if (typeof Meteor === 'undefined' || Meteor.isServer) {
var result = load() || {};
var mainModule = result.mainModule;
var exports = result.exports;
if (mainModule && mainModule.asyncMainModule === ASYNC_MAIN_MODULE) {
return mainModule.then(function (mainExports) {
console.log('defineAsync', name, mainModule, exports);
self._define(name, mainExports, exports);
});
}
self._define(name, mainModule, exports);
// console.log('define', name, mainModule, exports);
return;
// this._packageQueue.push({
// name: name,
// load: load,
// deps: deps
// });
// if (!this._running) {
// this._running = true;
// this._packageQueue.unshift().load();
// }
}
// TODO: implement
}
function isThenable(value) {
return typeof value === 'object' && value !== null &&
typeof value.then === 'function';
}
PRp._evaluateEagerModules = function(require, paths, mainModuleIndex) {
let index = -1;
let result;
let promise;
let resolve;
function evaluateNext() {
index += 1;
if (index === paths.length) {
if (resolve) {
resolve(result);
}
return;
}
let path = paths[index];
let exports = require(path);
if (isThenable(exports)) {
if (!promise) {
promise = new Promise(_resolve => resolve = _resolve);
promise.asyncMainModule = ASYNC_MAIN_MODULE;
}
exports.then(resolvedExports => {
if (index === mainModuleIndex) {
result = resolvedExports;
}
evaluateNext();
});
// TODO: handle error
} else {
if (index === mainModuleIndex) {
result = exports;
}
evaluateNext();
}
}
evaluateNext();
return promise ? promise : result;
}
// Initialize the Package namespace used by all Meteor packages.
global.Package = new PackageRegistry();

View File

@@ -439,7 +439,7 @@ Object.assign(Module.prototype, {
assert.ok(_.isNumber(moduleCount));
assert.ok(_.isNumber(sourceWidth));
let exportsName;
let exportsIndex = -1;
// Now that we have installed everything in this package or
// application, first evaluate the bare files, then require the
@@ -458,21 +458,33 @@ Object.assign(Module.prototype, {
});
if (eagerModuleFiles.length > 0) {
_.each(eagerModuleFiles, file => {
let code = 'Package._evaluateEagerModules(require,[';
let paths = eagerModuleFiles.map((file, index) => {
if (file.mainModule) {
exportsName = "exports";
exportsIndex = index;
}
chunks.push(
file.mainModule ? "\nvar " + exportsName + " = " : "\n",
"require(",
JSON.stringify(file.absModuleId),
");"
);
return JSON.stringify(file.absModuleId);
});
code += paths.join(',\n ');
code += ']';
if (exportsIndex !== -1) {
code += `, ${exportsIndex}`;
}
code += ');';
if (exportsIndex !== -1) {
code = '\nvar exports = ' + code;
}
chunks.push(code);
}
return exportsName;
return exportsIndex === -1 ? undefined : 'exports';
}
});
@@ -964,8 +976,22 @@ var SOURCE_MAP_INSTRUCTIONS_COMMENT = banner([
var getHeader = function (options) {
var chunks = [];
// TODO: find a better check that also works for packages that
// load before the Meteor package
if (options.name !== 'meteor') {
let depsCode = Object.values(options.imports).map(k => JSON.stringify(k)).join(', ');
chunks.push(
`Package.load("${options.name}", [`,
depsCode,
'], function () {'
);
} else {
chunks.push("(function() {\n\n")
}
chunks.push(
"(function () {\n\n",
getImportCode(options.imports, "/* Imports */\n", false),
);
@@ -1015,33 +1041,47 @@ var getFooter = function ({
name,
exported,
exportsName,
imports
}) {
var chunks = [];
if (name && exported) {
if (name === 'meteor') {
chunks.push("Package._define(" + JSON.stringify(name) + ", ");
if (!_.isEmpty(exported)) {
const scratch = {};
_.each(exported, symbol => scratch[symbol] = symbol);
const symbolTree = writeSymbolTree(buildSymbolTree(scratch));
chunks.push(symbolTree);
}
chunks.push(');\n');
} else if (exported || exportsName) {
chunks.push("\n\n/* Exports */\n");
chunks.push('return {\n');
if (exportsName) {
chunks.push(` mainModule: ${exportsName},`);
}
// Even if there are no exports, we need to define Package.foo,
// because the existence of Package.foo is how another package
// (e.g., one that weakly depends on foo) can tell if foo is loaded.
chunks.push("Package._define(" + JSON.stringify(name));
if (exportsName) {
// If we have an exports object, use it as Package[name].
chunks.push(", ", exportsName);
}
if (! _.isEmpty(exported)) {
const scratch = {};
_.each(exported, symbol => scratch[symbol] = symbol);
const symbolTree = writeSymbolTree(buildSymbolTree(scratch));
chunks.push(", ", symbolTree);
chunks.push("exports: ", symbolTree);
}
chunks.push(");\n");
chunks.push('};\n');
}
if (name !== 'meteor') {
chunks.push("\n});\n");
} else {
chunks.push("\n})();\n");
}
chunks.push("\n})();\n");
return chunks.join('');
};
@@ -1150,6 +1190,7 @@ export var fullLink = Profile("linker.fullLink", function (inputFiles, {
// Otherwise we're making a package and we have to actually combine the files
// into a single scope.
var header = getHeader({
name,
imports,
packageVariables: _.union(assignedVariables, declaredExports)
});
@@ -1164,7 +1205,8 @@ export var fullLink = Profile("linker.fullLink", function (inputFiles, {
var footer = getFooter({
exported: declaredExports,
exportsName,
name
name,
imports
});
if (includeSourceMapInstructions) {