1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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 };
}
|