summaryrefslogtreecommitdiff
path: root/js/runner.ts
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2018-11-20 10:51:35 +1100
committerRyan Dahl <ry@tinyclouds.org>2018-11-20 08:46:56 -0800
commit3d03f5b0cb3c513e449f3aaa5d35c493b72f47b4 (patch)
treece5d20849c3bc10aca0708f8d3910c8ab4cd82fd /js/runner.ts
parent3597d6859cacf0ef3ef935ad7d4e98d4df4a15ff (diff)
Split Runner from Compiler
Diffstat (limited to 'js/runner.ts')
-rw-r--r--js/runner.ts187
1 files changed, 187 insertions, 0 deletions
diff --git a/js/runner.ts b/js/runner.ts
new file mode 100644
index 000000000..11c47709d
--- /dev/null
+++ b/js/runner.ts
@@ -0,0 +1,187 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+// tslint:disable-next-line:no-circular-imports
+import * as deno from "./deno";
+import { globalEval } from "./global_eval";
+import { assert, log } from "./util";
+
+// tslint:disable:no-any
+type AmdCallback = (...args: any[]) => void;
+type AmdDefine = (deps: ModuleSpecifier[], factory: AmdFactory) => void;
+type AmdErrback = (err: any) => void;
+type AmdFactory = (...args: any[]) => object | void;
+type AmdRequire = (
+ deps: ModuleSpecifier[],
+ callback: AmdCallback,
+ errback: AmdErrback
+) => void;
+// tslint:enable:no-any
+
+// tslint:disable-next-line:no-any
+type BuiltinMap = { [moduleSpecifier: string]: any };
+
+// Type aliases to make the code more readable
+type ContainingFile = string;
+type Filename = string;
+type ModuleSpecifier = string;
+type OutputCode = string;
+
+/** Internal representation of a module being loaded */
+class Module {
+ deps?: Filename[];
+ factory?: AmdFactory;
+
+ // tslint:disable-next-line:no-any
+ constructor(public filename: Filename, public exports: any) {}
+}
+
+/** External APIs which the runner depends upon to be able to retrieve
+ * transpiled modules.
+ */
+export interface CodeProvider {
+ /** Given a module specifier and a containing file, return the filename. */
+ getFilename(
+ moduleSpecifier: ModuleSpecifier,
+ containingFile: ContainingFile
+ ): Filename;
+
+ /** Given a filename, return the transpiled output code. */
+ getOutput(filename: Filename): OutputCode;
+}
+
+const window = globalEval("this");
+
+/** A class which can load and run modules into the current environment. */
+export class Runner {
+ private _globalEval = globalEval;
+ /** A map of modules indexed by filename. */
+ private _modules = new Map<Filename, Module>();
+ private _provider: CodeProvider;
+ /** Modules are placed in here to have their factories run after all the
+ * the dependencies have been collected.
+ */
+ private _runQueue: Module[] = [];
+
+ private _drainRunQueue(): void {
+ log("runner._drainRunQueue", this._runQueue.length);
+ let module: Module | undefined;
+ while ((module = this._runQueue.shift())) {
+ assert(module.factory != null, "Cannot run module without factory.");
+ // TypeScript always imports `exports` and mutates it directly, but the
+ // AMD specification allows values to be returned from the factory and
+ // is the case with JSON modules and potentially other future features.
+ const exports = module.factory!(...this._getFactoryArguments(module));
+ if (exports != null) {
+ module.exports = exports;
+ }
+ }
+ }
+
+ private _gatherDependencies(filename: Filename): void {
+ log("runner._gatherDependencies", filename);
+
+ if (this._modules.has(filename)) {
+ log("Module already exists:", filename);
+ return;
+ }
+
+ const module = new Module(filename, {});
+ this._modules.set(filename, module);
+
+ window.define = this._makeDefine(module);
+ this._globalEval(this._provider.getOutput(filename));
+ window.define = undefined;
+ }
+
+ // tslint:disable-next-line:no-any
+ private _getFactoryArguments(module: Module): any[] {
+ log("runner._getFactoryArguments", module.filename);
+ assert(module.deps != null, "Missing dependencies for module.");
+ return module.deps!.map(dep => {
+ if (dep === "require") {
+ return this._makeLocalRequire(module);
+ }
+ if (dep === "exports") {
+ return module.exports;
+ }
+ if (dep in Runner._builtins) {
+ return Runner._builtins[dep];
+ }
+ const depModule = this._modules.get(dep)!;
+ assert(dep != null, `Missing dependency "${dep}"`);
+ return depModule.exports;
+ });
+ }
+
+ private _makeDefine(module: Module): AmdDefine {
+ log("runner._makeDefine", module.filename);
+ return (deps: ModuleSpecifier[], factory: AmdFactory): void => {
+ module.factory = factory;
+ module.deps = deps.map(dep => {
+ if (dep === "require" || dep === "exports" || dep in Runner._builtins) {
+ return dep;
+ }
+ const depFilename = this._provider.getFilename(dep, module.filename);
+ if (!this._modules.get(depFilename)) {
+ this._gatherDependencies(depFilename);
+ }
+ return depFilename;
+ });
+ if (!this._runQueue.includes(module)) {
+ this._runQueue.push(module);
+ }
+ };
+ }
+
+ private _makeLocalRequire(module: Module): AmdRequire {
+ log("runner._makeLocalRequire", module.filename);
+ return (
+ deps: ModuleSpecifier[],
+ callback: AmdCallback,
+ errback: AmdErrback
+ ): void => {
+ log("runner._makeLocalRequire", deps);
+ assert(
+ deps.length === 1,
+ "Local require supports exactly one dependency."
+ );
+ const [moduleSpecifier] = deps;
+ try {
+ this.run(moduleSpecifier, module.filename);
+ const requiredFilename = this._provider.getFilename(
+ moduleSpecifier,
+ module.filename
+ );
+ const requiredModule = this._modules.get(requiredFilename)!;
+ assert(requiredModule != null);
+ callback(requiredModule.exports);
+ } catch (e) {
+ errback(e);
+ }
+ };
+ }
+
+ constructor(provider: CodeProvider) {
+ this._provider = provider;
+ }
+
+ /** Given a module specifier and the containing file, resolve the module and
+ * ensure that it is in the runtime environment, returning the exports of the
+ * module.
+ */
+ // tslint:disable-next-line:no-any
+ run(moduleSpecifier: ModuleSpecifier, containingFile: ContainingFile): any {
+ log("runner.run", moduleSpecifier, containingFile);
+ const filename = this._provider.getFilename(
+ moduleSpecifier,
+ containingFile
+ );
+ if (!this._modules.has(filename)) {
+ this._gatherDependencies(filename);
+ this._drainRunQueue();
+ }
+ return this._modules.get(filename)!.exports;
+ }
+
+ /** Builtin modules which can be loaded by user modules. */
+ private static _builtins: BuiltinMap = { deno };
+}