summaryrefslogtreecommitdiff
path: root/js/runtime.ts
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2018-07-06 11:20:35 -0400
committerRyan Dahl <ry@tinyclouds.org>2018-07-06 12:22:11 -0400
commitfe404dfce901356dc7a5d38ba70029c72a946f27 (patch)
treeb8ed0d3417e920da1fd4e6278046184d8205a237 /js/runtime.ts
parent21e1425656ccebb8d31da95acd83991fb7d728fd (diff)
Import ts file from prototype without change
From commit 559453cf6cc88283bcf8fdeccd387458f5c63165 Excluding v8worker.d.ts, main.ts, and deno.d.ts. Updates tslint.json to be original settings.
Diffstat (limited to 'js/runtime.ts')
-rw-r--r--js/runtime.ts339
1 files changed, 339 insertions, 0 deletions
diff --git a/js/runtime.ts b/js/runtime.ts
new file mode 100644
index 000000000..46538c80f
--- /dev/null
+++ b/js/runtime.ts
@@ -0,0 +1,339 @@
+// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
+// All rights reserved. MIT License.
+// Glossary
+// outputCode = generated javascript code
+// sourceCode = typescript code (or input javascript code)
+// moduleName = a resolved module name
+// fileName = an unresolved raw fileName.
+// for http modules , its the path to the locally downloaded
+// version.
+
+import * as ts from "typescript";
+import * as util from "./util";
+import { log } from "./util";
+import * as os from "./os";
+import * as sourceMaps from "./v8_source_maps";
+import { _global, globalEval } from "./globals";
+import * as deno from "./deno";
+
+const EOL = "\n";
+
+// tslint:disable-next-line:no-any
+export type AmdFactory = (...args: any[]) => undefined | object;
+export type AmdDefine = (deps: string[], factory: AmdFactory) => void;
+
+// Uncaught exceptions are sent to window.onerror by v8worker2.
+// https://git.io/vhOsf
+window.onerror = (message, source, lineno, colno, error) => {
+ // TODO Currently there is a bug in v8_source_maps.ts that causes a segfault
+ // if it is used within window.onerror. To workaround we uninstall the
+ // Error.prepareStackTrace handler. Users will get unmapped stack traces on
+ // uncaught exceptions until this issue is fixed.
+ Error.prepareStackTrace = null;
+ console.log(error.message, error.stack);
+ os.exit(1);
+};
+
+export function setup(mainJs: string, mainMap: string): void {
+ sourceMaps.install({
+ installPrepareStackTrace: true,
+ getGeneratedContents: (filename: string): string => {
+ if (filename === "/main.js") {
+ return mainJs;
+ } else if (filename === "/main.map") {
+ return mainMap;
+ } else {
+ const mod = FileModule.load(filename);
+ if (!mod) {
+ console.error("getGeneratedContents cannot find", filename);
+ }
+ return mod.outputCode;
+ }
+ }
+ });
+}
+
+// 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 NOT executed upon first load, only when
+// compileAndRun is called.
+export class FileModule {
+ scriptVersion: string;
+ readonly exports = {};
+
+ private static readonly map = new Map<string, FileModule>();
+ constructor(
+ readonly fileName: string,
+ readonly sourceCode = "",
+ public outputCode = ""
+ ) {
+ util.assert(
+ !FileModule.map.has(fileName),
+ `FileModule.map already has ${fileName}`
+ );
+ FileModule.map.set(fileName, this);
+ if (outputCode !== "") {
+ this.scriptVersion = "1";
+ }
+ }
+
+ compileAndRun(): void {
+ if (!this.outputCode) {
+ // If there is no cached outputCode, then compile the code.
+ util.assert(
+ this.sourceCode != null && this.sourceCode.length > 0,
+ `Have no source code from ${this.fileName}`
+ );
+ const compiler = Compiler.instance();
+ this.outputCode = compiler.compile(this.fileName);
+ os.codeCache(this.fileName, this.sourceCode, this.outputCode);
+ }
+ util.log("compileAndRun", this.sourceCode);
+ execute(this.fileName, this.outputCode);
+ }
+
+ static load(fileName: string): FileModule {
+ return this.map.get(fileName);
+ }
+
+ 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;
+ }
+}
+
+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 if (dep === "typescript") {
+ return ts;
+ } else if (dep === "deno") {
+ return deno;
+ } else {
+ const resolved = resolveModuleName(dep, fileName);
+ const depModule = FileModule.load(resolved);
+ depModule.compileAndRun();
+ return depModule.exports;
+ }
+ });
+ factory(...args);
+ };
+ return localDefine;
+}
+
+export function resolveModule(
+ moduleSpecifier: string,
+ containingFile: string
+): null | FileModule {
+ //util.log("resolveModule", { moduleSpecifier, containingFile });
+ util.assert(moduleSpecifier != null && moduleSpecifier.length > 0);
+ // We ask golang to sourceCodeFetch. It will load the sourceCode and if
+ // there is any outputCode cached, it will return that as well.
+ let fetchResponse;
+ try {
+ fetchResponse = os.codeFetch(moduleSpecifier, containingFile);
+ } catch (e) {
+ // TODO Only catch "no such file or directory" errors. Need error codes.
+ return null;
+ }
+ const { filename, sourceCode, outputCode } = fetchResponse;
+ if (sourceCode.length === 0) {
+ return null;
+ }
+ util.log("resolveModule sourceCode length ", sourceCode.length);
+ const m = FileModule.load(filename);
+ if (m != null) {
+ return m;
+ } else {
+ return new FileModule(filename, sourceCode, outputCode);
+ }
+}
+
+function resolveModuleName(
+ moduleSpecifier: string,
+ containingFile: string
+): string | undefined {
+ const mod = resolveModule(moduleSpecifier, containingFile);
+ if (mod) {
+ return mod.fileName;
+ } else {
+ return undefined;
+ }
+}
+
+function execute(fileName: string, outputCode: string): void {
+ util.assert(outputCode && outputCode.length > 0);
+ _global["define"] = makeDefine(fileName);
+ outputCode += `\n//# sourceURL=${fileName}`;
+ globalEval(outputCode);
+ _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$",
+ inlineSourceMap: true,
+ lib: ["es2017"],
+ inlineSources: true,
+ target: ts.ScriptTarget.ES2017
+ };
+ /*
+ allowJs: 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) {
+ const errMsg = ts.formatDiagnosticsWithColorAndContext(
+ diagnostics,
+ formatDiagnosticsHost
+ );
+ console.log(errMsg);
+ os.exit(1);
+ }
+
+ 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 = resolveModule(fileName, ".");
+ if (m == null) {
+ util.log("getScriptSnapshot", fileName, "NOT FOUND");
+ return undefined;
+ }
+ //const m = resolveModule(fileName, ".");
+ util.assert(m.sourceCode.length > 0);
+ return ts.ScriptSnapshot.fromString(m.sourceCode);
+ }
+
+ fileExists(fileName: string): boolean {
+ const m = resolveModule(fileName, ".");
+ const exists = m != null;
+ util.log("fileExist", fileName, exists);
+ return exists;
+ }
+
+ readFile(path: string, encoding?: string): string | undefined {
+ util.log("readFile", path);
+ throw Error("not implemented");
+ }
+
+ getNewLine() {
+ return EOL;
+ }
+
+ getCurrentDirectory() {
+ util.log("getCurrentDirectory");
+ return ".";
+ }
+
+ getCompilationSettings() {
+ util.log("getCompilationSettings");
+ return this.options;
+ }
+
+ getDefaultLibFileName(options: ts.CompilerOptions): string {
+ const fn = ts.getDefaultLibFileName(options);
+ util.log("getDefaultLibFileName", fn);
+ const m = resolveModule(fn, "/$asset$/");
+ return m.fileName;
+ }
+
+ resolveModuleNames(
+ moduleNames: string[],
+ containingFile: string,
+ reusedNames?: string[]
+ ): Array<ts.ResolvedModule | undefined> {
+ //util.log("resolveModuleNames", { moduleNames, reusedNames });
+ return moduleNames.map((name: string) => {
+ let resolvedFileName;
+ if (name === "deno") {
+ resolvedFileName = resolveModuleName("deno.d.ts", "/$asset$/");
+ } else if (name === "typescript") {
+ resolvedFileName = resolveModuleName("typescript.d.ts", "/$asset$/");
+ } else {
+ resolvedFileName = resolveModuleName(name, containingFile);
+ if (resolvedFileName == null) {
+ return undefined;
+ }
+ }
+ const isExternalLibraryImport = false;
+ return { resolvedFileName, isExternalLibraryImport };
+ });
+ }
+}
+
+const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
+ getCurrentDirectory(): string {
+ return ".";
+ },
+ getCanonicalFileName(fileName: string): string {
+ return fileName;
+ },
+ getNewLine(): string {
+ return EOL;
+ }
+};