node/lib/internal/loader/ModuleJob.js
guybedford d21a11dc23 module: resolve and instantiate loader pipeline hooks
This enables a --loader flag for Node, which can provide custom
"resolve" and "dynamicInstantiate" methods for custom ES module
loading.

In the process, module providers have been converted from classes
into functions and the module APIs have been made to pass URL strings
over objects.

PR-URL: https://github.com/nodejs/node/pull/15445
Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
2017-10-11 08:47:23 -05:00

106 lines
3.1 KiB
JavaScript

'use strict';
const { SafeSet, SafePromise } = require('internal/safe_globals');
const resolvedPromise = SafePromise.resolve();
class ModuleJob {
/**
* @param {module: ModuleWrap?, compiled: Promise} moduleProvider
*/
constructor(loader, url, moduleProvider) {
this.loader = loader;
this.error = null;
this.hadError = false;
// linked == promise for dependency jobs, with module populated,
// module wrapper linked
this.moduleProvider = moduleProvider;
this.modulePromise = this.moduleProvider(url);
this.module = undefined;
this.reflect = undefined;
const linked = async () => {
const dependencyJobs = [];
({ module: this.module,
reflect: this.reflect } = await this.modulePromise);
this.module.link(async (dependencySpecifier) => {
const dependencyJobPromise =
this.loader.getModuleJob(dependencySpecifier, url);
dependencyJobs.push(dependencyJobPromise);
const dependencyJob = await dependencyJobPromise;
return (await dependencyJob.modulePromise).module;
});
return SafePromise.all(dependencyJobs);
};
this.linked = linked();
// instantiated == deep dependency jobs wrappers instantiated,
// module wrapper instantiated
this.instantiated = undefined;
}
instantiate() {
if (this.instantiated) {
return this.instantiated;
}
return this.instantiated = new Promise(async (resolve, reject) => {
const jobsInGraph = new SafeSet();
let jobsReadyToInstantiate = 0;
// (this must be sync for counter to work)
const queueJob = (moduleJob) => {
if (jobsInGraph.has(moduleJob)) {
return;
}
jobsInGraph.add(moduleJob);
moduleJob.linked.then((dependencyJobs) => {
for (const dependencyJob of dependencyJobs) {
queueJob(dependencyJob);
}
checkComplete();
}, (e) => {
if (!this.hadError) {
this.error = e;
this.hadError = true;
}
checkComplete();
});
};
const checkComplete = () => {
if (++jobsReadyToInstantiate === jobsInGraph.size) {
// I believe we only throw once the whole tree is finished loading?
// or should the error bail early, leaving entire tree to still load?
if (this.hadError) {
reject(this.error);
} else {
try {
this.module.instantiate();
for (const dependencyJob of jobsInGraph) {
dependencyJob.instantiated = resolvedPromise;
}
resolve(this.module);
} catch (e) {
e.stack;
reject(e);
}
}
}
};
queueJob(this);
});
}
async run() {
const module = await this.instantiate();
try {
module.evaluate();
} catch (e) {
e.stack;
this.hadError = true;
this.error = e;
throw e;
}
return module;
}
}
Object.setPrototypeOf(ModuleJob.prototype, null);
module.exports = ModuleJob;