summaryrefslogtreecommitdiff
path: root/runtime.ts
diff options
context:
space:
mode:
Diffstat (limited to 'runtime.ts')
-rw-r--r--runtime.ts244
1 files changed, 244 insertions, 0 deletions
diff --git a/runtime.ts b/runtime.ts
new file mode 100644
index 000000000..1e031e7f7
--- /dev/null
+++ b/runtime.ts
@@ -0,0 +1,244 @@
+// Glossary
+// outputCode = generated javascript code
+// sourceCode = typescript code (or input javascript code)
+// fileName = an unresolved raw fileName.
+// moduleName = a resolved module name
+
+import * as ts from "typescript";
+import * as path from "path";
+import * as util from "./util";
+import { log } from "./util";
+import * as os from "./os";
+
+// This class represents a module. We call it FileModule to make it explicit
+// that each module represents a single file.
+// Access to FileModule instances should only be done thru the static method
+// FileModule.load(). FileModules are executed upon first load.
+export class FileModule {
+ scriptVersion: string = undefined;
+ sourceCode: string;
+ outputCode: string;
+ readonly exports = {};
+
+ private static readonly map = new Map<string, FileModule>();
+ private constructor(readonly fileName: string) {
+ FileModule.map.set(fileName, this);
+
+ assertValidFileName(this.fileName);
+
+ // Load typescript code (sourceCode) and maybe load compiled javascript
+ // (outputCode) from cache. If cache is empty, outputCode will be null.
+ const { sourceCode, outputCode } = os.sourceCodeFetch(this.fileName);
+ this.sourceCode = sourceCode;
+ this.outputCode = outputCode;
+ this.scriptVersion = "1";
+ }
+
+ compileAndRun() {
+ if (!this.outputCode) {
+ // If there is no cached outputCode, the compile the code.
+ util.assert(this.sourceCode && this.sourceCode.length > 0);
+ const compiler = Compiler.instance();
+ this.outputCode = compiler.compile(this.fileName);
+ os.sourceCodeCache(this.fileName, this.sourceCode, this.outputCode);
+ }
+ util.log("compileAndRun", this.sourceCode);
+ execute(this.fileName, this.outputCode);
+ }
+
+ static load(fileName: string): FileModule {
+ assertValidFileName(fileName);
+ let m = this.map.get(fileName);
+ if (m == null) {
+ m = new this(fileName);
+ util.assert(this.map.has(fileName));
+ }
+ return m;
+ }
+
+ static getScriptsWithSourceCode(): string[] {
+ const out = [];
+ for (const fn of this.map.keys()) {
+ const m = this.map.get(fn);
+ if (m.sourceCode) {
+ out.push(fn);
+ }
+ }
+ return out;
+ }
+}
+
+function assertValidFileName(fileName: string): void {
+ if (fileName !== "lib.d.ts") {
+ util.assert(fileName[0] === "/", `fileName must be absolute: ${fileName}`);
+ }
+}
+
+// tslint:disable-next-line:no-any
+type AmdFactory = (...args: any[]) => undefined | object;
+type AmdDefine = (deps: string[], factory: AmdFactory) => void;
+
+export function makeDefine(fileName: string): AmdDefine {
+ const localDefine = (deps: string[], factory: AmdFactory): void => {
+ const localRequire = (x: string) => {
+ log("localRequire", x);
+ };
+ const currentModule = FileModule.load(fileName);
+ const localExports = currentModule.exports;
+ log("localDefine", fileName, deps, localExports);
+ const args = deps.map(dep => {
+ if (dep === "require") {
+ return localRequire;
+ } else if (dep === "exports") {
+ return localExports;
+ } else {
+ dep = resolveModuleName(dep, fileName);
+ const depModule = FileModule.load(dep);
+ depModule.compileAndRun();
+ return depModule.exports;
+ }
+ });
+ factory(...args);
+ };
+ return localDefine;
+}
+
+function resolveModuleName(fileName: string, contextFileName: string): string {
+ return path.resolve(path.dirname(contextFileName), fileName);
+}
+
+function execute(fileName: string, outputCode: string): void {
+ util.assert(outputCode && outputCode.length > 0);
+ util._global["define"] = makeDefine(fileName);
+ util.globalEval(outputCode);
+ util._global["define"] = null;
+}
+
+// This is a singleton class. Use Compiler.instance() to access.
+class Compiler {
+ options: ts.CompilerOptions = {
+ allowJs: true,
+ module: ts.ModuleKind.AMD,
+ outDir: "$deno$"
+ };
+ /*
+ allowJs: true,
+ inlineSourceMap: true,
+ inlineSources: true,
+ module: ts.ModuleKind.AMD,
+ noEmit: false,
+ outDir: '$deno$',
+ */
+ private service: ts.LanguageService;
+
+ private constructor() {
+ const host = new TypeScriptHost(this.options);
+ this.service = ts.createLanguageService(host);
+ }
+
+ private static _instance: Compiler;
+ static instance(): Compiler {
+ return this._instance || (this._instance = new this());
+ }
+
+ compile(fileName: string): string {
+ const output = this.service.getEmitOutput(fileName);
+
+ // Get the relevant diagnostics - this is 3x faster than
+ // `getPreEmitDiagnostics`.
+ const diagnostics = this.service
+ .getCompilerOptionsDiagnostics()
+ .concat(this.service.getSyntacticDiagnostics(fileName))
+ .concat(this.service.getSemanticDiagnostics(fileName));
+ if (diagnostics.length > 0) {
+ throw Error("diagnotics");
+ }
+
+ util.log("compile output", output);
+ util.assert(!output.emitSkipped);
+
+ const outputCode = output.outputFiles[0].text;
+ // let sourceMapCode = output.outputFiles[0].text;
+ return outputCode;
+ }
+}
+
+// Create the compiler host for type checking.
+class TypeScriptHost implements ts.LanguageServiceHost {
+ constructor(readonly options: ts.CompilerOptions) {}
+
+ getScriptFileNames(): string[] {
+ const keys = FileModule.getScriptsWithSourceCode();
+ util.log("getScriptFileNames", keys);
+ return keys;
+ }
+
+ getScriptVersion(fileName: string): string {
+ util.log("getScriptVersion", fileName);
+ const m = FileModule.load(fileName);
+ return m.scriptVersion;
+ }
+
+ getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
+ util.log("getScriptSnapshot", fileName);
+ const m = FileModule.load(fileName);
+ if (m.sourceCode) {
+ return ts.ScriptSnapshot.fromString(m.sourceCode);
+ } else {
+ return undefined;
+ }
+ }
+
+ fileExists(fileName: string): boolean {
+ throw Error("not implemented");
+ }
+
+ readFile(path: string, encoding?: string): string | undefined {
+ util.log("readFile", path);
+ throw Error("not implemented");
+ }
+
+ getNewLine() {
+ const EOL = "\n";
+ return EOL;
+ }
+
+ getCurrentDirectory() {
+ util.log("getCurrentDirectory");
+ return ".";
+ }
+
+ getCompilationSettings() {
+ util.log("getCompilationSettings");
+ return this.options;
+ }
+
+ getDefaultLibFileName(options: ts.CompilerOptions): string {
+ util.log("getDefaultLibFileName");
+ return ts.getDefaultLibFileName(options);
+ }
+
+ resolveModuleNames(
+ moduleNames: string[],
+ containingFile: string,
+ reusedNames?: string[]
+ ): Array<ts.ResolvedModule | undefined> {
+ util.log("resolveModuleNames", { moduleNames, reusedNames });
+ return moduleNames.map((name: string) => {
+ if (
+ name.startsWith("/") ||
+ name.startsWith("http://") ||
+ name.startsWith("https://")
+ ) {
+ throw Error("Non-relative imports not yet supported.");
+ } else {
+ // Relative import.
+ const containingDir = path.dirname(containingFile);
+ const resolvedFileName = path.join(containingDir, name);
+ util.log("relative import", { containingFile, name, resolvedFileName });
+ const isExternalLibraryImport = false;
+ return { resolvedFileName, isExternalLibraryImport };
+ }
+ });
+ }
+}