diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2018-07-06 11:20:35 -0400 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2018-07-06 12:22:11 -0400 |
commit | fe404dfce901356dc7a5d38ba70029c72a946f27 (patch) | |
tree | b8ed0d3417e920da1fd4e6278046184d8205a237 /js/runtime.ts | |
parent | 21e1425656ccebb8d31da95acd83991fb7d728fd (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.ts | 339 |
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; + } +}; |