diff options
Diffstat (limited to 'cli/tsc/99_main_compiler.js')
-rw-r--r-- | cli/tsc/99_main_compiler.js | 1867 |
1 files changed, 1867 insertions, 0 deletions
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js new file mode 100644 index 000000000..d64896862 --- /dev/null +++ b/cli/tsc/99_main_compiler.js @@ -0,0 +1,1867 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// This module is the entry point for "compiler" isolate, ie. the one +// that is created when Deno needs to compile TS/WASM to JS. +// +// It provides a single functions that should be called by Rust: +// - `bootstrapTsCompilerRuntime` +// This functions must be called when creating isolate +// to properly setup runtime. + +// Removes the `__proto__` for security reasons. This intentionally makes +// Deno non compliant with ECMA-262 Annex B.2.2.1 +// +// eslint-disable-next-line @typescript-eslint/no-explicit-any +delete Object.prototype.__proto__; + +((window) => { + const core = Deno.core; + const { bold, cyan, yellow } = window.__bootstrap.colors; + const { assert, log, notImplemented } = window.__bootstrap.util; + const { DiagnosticCategory } = window.__bootstrap.diagnostics; + + const unstableDenoGlobalProperties = [ + "umask", + "linkSync", + "link", + "symlinkSync", + "symlink", + "loadavg", + "osRelease", + "openPlugin", + "DiagnosticCategory", + "DiagnosticMessageChain", + "DiagnosticItem", + "Diagnostic", + "formatDiagnostics", + "CompilerOptions", + "TranspileOnlyResult", + "transpileOnly", + "compile", + "bundle", + "Location", + "applySourceMap", + "LinuxSignal", + "MacOSSignal", + "Signal", + "SignalStream", + "signal", + "signals", + "setRaw", + "utimeSync", + "utime", + "ShutdownMode", + "shutdown", + "DatagramConn", + "UnixListenOptions", + "listen", + "listenDatagram", + "UnixConnectOptions", + "connect", + "StartTlsOptions", + "startTls", + "kill", + "PermissionName", + "PermissionState", + "RunPermissionDescriptor", + "ReadPermissionDescriptor", + "WritePermissionDescriptor", + "NetPermissionDescriptor", + "EnvPermissionDescriptor", + "PluginPermissionDescriptor", + "HrtimePermissionDescriptor", + "PermissionDescriptor", + "Permissions", + "PermissionStatus", + "hostname", + "ppid", + ]; + + function transformMessageText(messageText, code) { + switch (code) { + case 2339: { + const property = messageText + .replace(/^Property '/, "") + .replace(/' does not exist on type 'typeof Deno'\./, ""); + + if ( + messageText.endsWith("on type 'typeof Deno'.") && + unstableDenoGlobalProperties.includes(property) + ) { + return `${messageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag?`; + } + break; + } + case 2551: { + const suggestionMessagePattern = / Did you mean '(.+)'\?$/; + const property = messageText + .replace(/^Property '/, "") + .replace(/' does not exist on type 'typeof Deno'\./, "") + .replace(suggestionMessagePattern, ""); + const suggestion = messageText.match(suggestionMessagePattern); + const replacedMessageText = messageText.replace( + suggestionMessagePattern, + "", + ); + if (suggestion && unstableDenoGlobalProperties.includes(property)) { + const suggestedProperty = suggestion[1]; + return `${replacedMessageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean '${suggestedProperty}'?`; + } + break; + } + } + + return messageText; + } + + function fromDiagnosticCategory( + category, + ) { + switch (category) { + case ts.DiagnosticCategory.Error: + return DiagnosticCategory.Error; + case ts.DiagnosticCategory.Message: + return DiagnosticCategory.Info; + case ts.DiagnosticCategory.Suggestion: + return DiagnosticCategory.Suggestion; + case ts.DiagnosticCategory.Warning: + return DiagnosticCategory.Warning; + default: + throw new Error( + `Unexpected DiagnosticCategory: "${category}"/"${ + ts.DiagnosticCategory[category] + }"`, + ); + } + } + + function getSourceInformation( + sourceFile, + start, + length, + ) { + const scriptResourceName = sourceFile.fileName; + const { + line: lineNumber, + character: startColumn, + } = sourceFile.getLineAndCharacterOfPosition(start); + const endPosition = sourceFile.getLineAndCharacterOfPosition( + start + length, + ); + const endColumn = lineNumber === endPosition.line + ? endPosition.character + : startColumn; + const lastLineInFile = sourceFile.getLineAndCharacterOfPosition( + sourceFile.text.length, + ).line; + const lineStart = sourceFile.getPositionOfLineAndCharacter(lineNumber, 0); + const lineEnd = lineNumber < lastLineInFile + ? sourceFile.getPositionOfLineAndCharacter(lineNumber + 1, 0) + : sourceFile.text.length; + const sourceLine = sourceFile.text + .slice(lineStart, lineEnd) + .replace(/\s+$/g, "") + .replace("\t", " "); + return { + sourceLine, + lineNumber, + scriptResourceName, + startColumn, + endColumn, + }; + } + + function fromDiagnosticMessageChain( + messageChain, + ) { + if (!messageChain) { + return undefined; + } + + return messageChain.map(({ messageText, code, category, next }) => { + const message = transformMessageText(messageText, code); + return { + message, + code, + category: fromDiagnosticCategory(category), + next: fromDiagnosticMessageChain(next), + }; + }); + } + + function parseDiagnostic( + item, + ) { + const { + messageText, + category: sourceCategory, + code, + file, + start: startPosition, + length, + } = item; + const sourceInfo = file && startPosition && length + ? getSourceInformation(file, startPosition, length) + : undefined; + const endPosition = startPosition && length + ? startPosition + length + : undefined; + const category = fromDiagnosticCategory(sourceCategory); + + let message; + let messageChain; + if (typeof messageText === "string") { + message = transformMessageText(messageText, code); + } else { + message = transformMessageText(messageText.messageText, messageText.code); + messageChain = fromDiagnosticMessageChain([messageText])[0]; + } + + const base = { + message, + messageChain, + code, + category, + startPosition, + endPosition, + }; + + return sourceInfo ? { ...base, ...sourceInfo } : base; + } + + function parseRelatedInformation( + relatedInformation, + ) { + const result = []; + for (const item of relatedInformation) { + result.push(parseDiagnostic(item)); + } + return result; + } + + function fromTypeScriptDiagnostic( + diagnostics, + ) { + const items = []; + for (const sourceDiagnostic of diagnostics) { + const item = parseDiagnostic(sourceDiagnostic); + if (sourceDiagnostic.relatedInformation) { + item.relatedInformation = parseRelatedInformation( + sourceDiagnostic.relatedInformation, + ); + } + items.push(item); + } + return { items }; + } + + // We really don't want to depend on JSON dispatch during snapshotting, so + // this op exchanges strings with Rust as raw byte arrays. + function getAsset(name) { + const opId = core.ops()["op_fetch_asset"]; + const sourceCodeBytes = core.dispatch(opId, core.encode(name)); + return core.decode(sourceCodeBytes); + } + + // Constants used by `normalizeString` and `resolvePath` + const CHAR_DOT = 46; /* . */ + const CHAR_FORWARD_SLASH = 47; /* / */ + // Using incremental compile APIs requires that all + // paths must be either relative or absolute. Since + // analysis in Rust operates on fully resolved URLs, + // it makes sense to use the same scheme here. + const ASSETS = "asset://"; + const OUT_DIR = "deno://"; + // This constant is passed to compiler settings when + // doing incremental compiles. Contents of this + // file are passed back to Rust and saved to $DENO_DIR. + const TS_BUILD_INFO = "cache:///tsbuildinfo.json"; + + // TODO(Bartlomieju): this check should be done in Rust + const IGNORED_COMPILER_OPTIONS = [ + "allowSyntheticDefaultImports", + "allowUmdGlobalAccess", + "assumeChangesOnlyAffectDirectDependencies", + "baseUrl", + "build", + "composite", + "declaration", + "declarationDir", + "declarationMap", + "diagnostics", + "downlevelIteration", + "emitBOM", + "emitDeclarationOnly", + "esModuleInterop", + "extendedDiagnostics", + "forceConsistentCasingInFileNames", + "generateCpuProfile", + "help", + "importHelpers", + "incremental", + "inlineSourceMap", + "inlineSources", + "init", + "listEmittedFiles", + "listFiles", + "mapRoot", + "maxNodeModuleJsDepth", + "module", + "moduleResolution", + "newLine", + "noEmit", + "noEmitHelpers", + "noEmitOnError", + "noLib", + "noResolve", + "out", + "outDir", + "outFile", + "paths", + "preserveSymlinks", + "preserveWatchOutput", + "pretty", + "rootDir", + "rootDirs", + "showConfig", + "skipDefaultLibCheck", + "skipLibCheck", + "sourceMap", + "sourceRoot", + "stripInternal", + "target", + "traceResolution", + "tsBuildInfoFile", + "types", + "typeRoots", + "version", + "watch", + ]; + + const DEFAULT_BUNDLER_OPTIONS = { + allowJs: true, + inlineSourceMap: false, + module: ts.ModuleKind.System, + outDir: undefined, + outFile: `${OUT_DIR}/bundle.js`, + // disabled until we have effective way to modify source maps + sourceMap: false, + }; + + const DEFAULT_INCREMENTAL_COMPILE_OPTIONS = { + allowJs: false, + allowNonTsExtensions: true, + checkJs: false, + esModuleInterop: true, + incremental: true, + inlineSourceMap: true, + jsx: ts.JsxEmit.React, + module: ts.ModuleKind.ESNext, + outDir: OUT_DIR, + resolveJsonModule: true, + sourceMap: false, + strict: true, + stripComments: true, + target: ts.ScriptTarget.ESNext, + tsBuildInfoFile: TS_BUILD_INFO, + }; + + const DEFAULT_COMPILE_OPTIONS = { + allowJs: false, + allowNonTsExtensions: true, + checkJs: false, + esModuleInterop: true, + jsx: ts.JsxEmit.React, + module: ts.ModuleKind.ESNext, + outDir: OUT_DIR, + sourceMap: true, + strict: true, + removeComments: true, + target: ts.ScriptTarget.ESNext, + }; + + const DEFAULT_TRANSPILE_OPTIONS = { + esModuleInterop: true, + inlineSourceMap: true, + jsx: ts.JsxEmit.React, + module: ts.ModuleKind.ESNext, + removeComments: true, + target: ts.ScriptTarget.ESNext, + }; + + const DEFAULT_RUNTIME_COMPILE_OPTIONS = { + outDir: undefined, + }; + + const DEFAULT_RUNTIME_TRANSPILE_OPTIONS = { + esModuleInterop: true, + module: ts.ModuleKind.ESNext, + sourceMap: true, + scriptComments: true, + target: ts.ScriptTarget.ESNext, + }; + + const CompilerHostTarget = { + Main: "main", + Runtime: "runtime", + Worker: "worker", + }; + + // Warning! The values in this enum are duplicated in `cli/msg.rs` + // Update carefully! + const MediaType = { + 0: "JavaScript", + 1: "JSX", + 2: "TypeScript", + 3: "TSX", + 4: "Json", + 5: "Wasm", + 6: "Unknown", + JavaScript: 0, + JSX: 1, + TypeScript: 2, + TSX: 3, + Json: 4, + Wasm: 5, + Unknown: 6, + }; + + function getExtension(fileName, mediaType) { + switch (mediaType) { + case MediaType.JavaScript: + return ts.Extension.Js; + case MediaType.JSX: + return ts.Extension.Jsx; + case MediaType.TypeScript: + return fileName.endsWith(".d.ts") ? ts.Extension.Dts : ts.Extension.Ts; + case MediaType.TSX: + return ts.Extension.Tsx; + case MediaType.Wasm: + // Custom marker for Wasm type. + return ts.Extension.Js; + case MediaType.Unknown: + default: + throw TypeError( + `Cannot resolve extension for "${fileName}" with mediaType "${ + MediaType[mediaType] + }".`, + ); + } + } + + /** A global cache of module source files that have been loaded. + * This cache will be rewritten to be populated on compiler startup + * with files provided from Rust in request message. + */ + const SOURCE_FILE_CACHE = new Map(); + /** A map of maps which cache resolved specifier for each import in a file. + * This cache is used so `resolveModuleNames` ops is called as few times + * as possible. + * + * First map's key is "referrer" URL ("file://a/b/c/mod.ts") + * Second map's key is "raw" import specifier ("./foo.ts") + * Second map's value is resolved import URL ("file:///a/b/c/foo.ts") + */ + const RESOLVED_SPECIFIER_CACHE = new Map(); + + function configure( + defaultOptions, + source, + path, + cwd, + ) { + const { config, error } = ts.parseConfigFileTextToJson(path, source); + if (error) { + return { diagnostics: [error], options: defaultOptions }; + } + const { options, errors } = ts.convertCompilerOptionsFromJson( + config.compilerOptions, + cwd, + ); + const ignoredOptions = []; + for (const key of Object.keys(options)) { + if ( + IGNORED_COMPILER_OPTIONS.includes(key) && + (!(key in defaultOptions) || options[key] !== defaultOptions[key]) + ) { + ignoredOptions.push(key); + delete options[key]; + } + } + return { + options: Object.assign({}, defaultOptions, options), + ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined, + diagnostics: errors.length ? errors : undefined, + }; + } + + class SourceFile { + constructor(json) { + this.processed = false; + Object.assign(this, json); + this.extension = getExtension(this.url, this.mediaType); + } + + static addToCache(json) { + if (SOURCE_FILE_CACHE.has(json.url)) { + throw new TypeError("SourceFile already exists"); + } + const sf = new SourceFile(json); + SOURCE_FILE_CACHE.set(sf.url, sf); + return sf; + } + + static getCached(url) { + return SOURCE_FILE_CACHE.get(url); + } + + static cacheResolvedUrl( + resolvedUrl, + rawModuleSpecifier, + containingFile, + ) { + containingFile = containingFile || ""; + let innerCache = RESOLVED_SPECIFIER_CACHE.get(containingFile); + if (!innerCache) { + innerCache = new Map(); + RESOLVED_SPECIFIER_CACHE.set(containingFile, innerCache); + } + innerCache.set(rawModuleSpecifier, resolvedUrl); + } + + static getResolvedUrl( + moduleSpecifier, + containingFile, + ) { + const containingCache = RESOLVED_SPECIFIER_CACHE.get(containingFile); + if (containingCache) { + return containingCache.get(moduleSpecifier); + } + return undefined; + } + } + + function getAssetInternal(filename) { + const lastSegment = filename.split("/").pop(); + const url = ts.libMap.has(lastSegment) + ? ts.libMap.get(lastSegment) + : lastSegment; + const sourceFile = SourceFile.getCached(url); + if (sourceFile) { + return sourceFile; + } + const name = url.includes(".") ? url : `${url}.d.ts`; + const sourceCode = getAsset(name); + return SourceFile.addToCache({ + url, + filename: `${ASSETS}/${name}`, + mediaType: MediaType.TypeScript, + versionHash: "1", + sourceCode, + }); + } + + class Host { + #options = DEFAULT_COMPILE_OPTIONS; + #target = ""; + #writeFile = null; + /* Deno specific APIs */ + + constructor({ + bundle = false, + incremental = false, + target, + unstable, + writeFile, + }) { + this.#target = target; + this.#writeFile = writeFile; + if (bundle) { + // options we need to change when we are generating a bundle + Object.assign(this.#options, DEFAULT_BUNDLER_OPTIONS); + } else if (incremental) { + Object.assign(this.#options, DEFAULT_INCREMENTAL_COMPILE_OPTIONS); + } + if (unstable) { + this.#options.lib = [ + target === CompilerHostTarget.Worker + ? "lib.deno.worker.d.ts" + : "lib.deno.window.d.ts", + "lib.deno.unstable.d.ts", + ]; + } + } + + get options() { + return this.#options; + } + + configure( + cwd, + path, + configurationText, + ) { + log("compiler::host.configure", path); + const { options, ...result } = configure( + this.#options, + configurationText, + path, + cwd, + ); + this.#options = options; + return result; + } + + mergeOptions(...options) { + Object.assign(this.#options, ...options); + return Object.assign({}, this.#options); + } + + /* TypeScript CompilerHost APIs */ + + fileExists(_fileName) { + return notImplemented(); + } + + getCanonicalFileName(fileName) { + return fileName; + } + + getCompilationSettings() { + log("compiler::host.getCompilationSettings()"); + return this.#options; + } + + getCurrentDirectory() { + return ""; + } + + getDefaultLibFileName(_options) { + log("compiler::host.getDefaultLibFileName()"); + switch (this.#target) { + case CompilerHostTarget.Main: + case CompilerHostTarget.Runtime: + return `${ASSETS}/lib.deno.window.d.ts`; + case CompilerHostTarget.Worker: + return `${ASSETS}/lib.deno.worker.d.ts`; + } + } + + getNewLine() { + return "\n"; + } + + getSourceFile( + fileName, + languageVersion, + onError, + shouldCreateNewSourceFile, + ) { + log("compiler::host.getSourceFile", fileName); + try { + assert(!shouldCreateNewSourceFile); + const sourceFile = fileName.startsWith(ASSETS) + ? getAssetInternal(fileName) + : SourceFile.getCached(fileName); + assert(sourceFile != null); + if (!sourceFile.tsSourceFile) { + assert(sourceFile.sourceCode != null); + const tsSourceFileName = fileName.startsWith(ASSETS) + ? sourceFile.filename + : fileName; + + sourceFile.tsSourceFile = ts.createSourceFile( + tsSourceFileName, + sourceFile.sourceCode, + languageVersion, + ); + sourceFile.tsSourceFile.version = sourceFile.versionHash; + delete sourceFile.sourceCode; + } + return sourceFile.tsSourceFile; + } catch (e) { + if (onError) { + onError(String(e)); + } else { + throw e; + } + return undefined; + } + } + + readFile(_fileName) { + return notImplemented(); + } + + resolveModuleNames( + moduleNames, + containingFile, + ) { + log("compiler::host.resolveModuleNames", { + moduleNames, + containingFile, + }); + const resolved = moduleNames.map((specifier) => { + const maybeUrl = SourceFile.getResolvedUrl(specifier, containingFile); + + log("compiler::host.resolveModuleNames maybeUrl", { + specifier, + maybeUrl, + }); + + let sourceFile = undefined; + + if (specifier.startsWith(ASSETS)) { + sourceFile = getAssetInternal(specifier); + } else if (typeof maybeUrl !== "undefined") { + sourceFile = SourceFile.getCached(maybeUrl); + } + + if (!sourceFile) { + return undefined; + } + + return { + resolvedFileName: sourceFile.url, + isExternalLibraryImport: specifier.startsWith(ASSETS), + extension: sourceFile.extension, + }; + }); + log(resolved); + return resolved; + } + + useCaseSensitiveFileNames() { + return true; + } + + writeFile( + fileName, + data, + _writeByteOrderMark, + _onError, + sourceFiles, + ) { + log("compiler::host.writeFile", fileName); + this.#writeFile(fileName, data, sourceFiles); + } + } + + class IncrementalCompileHost extends Host { + #buildInfo = ""; + + constructor(options) { + super({ ...options, incremental: true }); + const { buildInfo } = options; + if (buildInfo) { + this.#buildInfo = buildInfo; + } + } + + readFile(fileName) { + if (fileName == TS_BUILD_INFO) { + return this.#buildInfo; + } + throw new Error("unreachable"); + } + } + + // NOTE: target doesn't really matter here, + // this is in fact a mock host created just to + // load all type definitions and snapshot them. + let SNAPSHOT_HOST = new Host({ + target: CompilerHostTarget.Main, + writeFile() {}, + }); + const SNAPSHOT_COMPILER_OPTIONS = SNAPSHOT_HOST.getCompilationSettings(); + + // This is a hacky way of adding our libs to the libs available in TypeScript() + // as these are internal APIs of TypeScript which maintain valid libs + ts.libs.push("deno.ns", "deno.window", "deno.worker", "deno.shared_globals"); + ts.libMap.set("deno.ns", "lib.deno.ns.d.ts"); + ts.libMap.set("deno.window", "lib.deno.window.d.ts"); + ts.libMap.set("deno.worker", "lib.deno.worker.d.ts"); + ts.libMap.set("deno.shared_globals", "lib.deno.shared_globals.d.ts"); + ts.libMap.set("deno.unstable", "lib.deno.unstable.d.ts"); + + // this pre-populates the cache at snapshot time of our library files, so they + // are available in the future when needed. + SNAPSHOT_HOST.getSourceFile( + `${ASSETS}/lib.deno.ns.d.ts`, + ts.ScriptTarget.ESNext, + ); + SNAPSHOT_HOST.getSourceFile( + `${ASSETS}/lib.deno.window.d.ts`, + ts.ScriptTarget.ESNext, + ); + SNAPSHOT_HOST.getSourceFile( + `${ASSETS}/lib.deno.worker.d.ts`, + ts.ScriptTarget.ESNext, + ); + SNAPSHOT_HOST.getSourceFile( + `${ASSETS}/lib.deno.shared_globals.d.ts`, + ts.ScriptTarget.ESNext, + ); + SNAPSHOT_HOST.getSourceFile( + `${ASSETS}/lib.deno.unstable.d.ts`, + ts.ScriptTarget.ESNext, + ); + + // We never use this program; it's only created + // during snapshotting to hydrate and populate + // source file cache with lib declaration files. + const _TS_SNAPSHOT_PROGRAM = ts.createProgram({ + rootNames: [`${ASSETS}/bootstrap.ts`], + options: SNAPSHOT_COMPILER_OPTIONS, + host: SNAPSHOT_HOST, + }); + + // Derference the snapshot host so it can be GCed + SNAPSHOT_HOST = undefined; + + // This function is called only during snapshotting process + const SYSTEM_LOADER = getAsset("system_loader.js"); + const SYSTEM_LOADER_ES5 = getAsset("system_loader_es5.js"); + + function buildLocalSourceFileCache( + sourceFileMap, + ) { + for (const entry of Object.values(sourceFileMap)) { + assert(entry.sourceCode.length > 0); + SourceFile.addToCache({ + url: entry.url, + filename: entry.url, + mediaType: entry.mediaType, + sourceCode: entry.sourceCode, + versionHash: entry.versionHash, + }); + + for (const importDesc of entry.imports) { + let mappedUrl = importDesc.resolvedSpecifier; + const importedFile = sourceFileMap[importDesc.resolvedSpecifier]; + assert(importedFile); + const isJsOrJsx = importedFile.mediaType === MediaType.JavaScript || + importedFile.mediaType === MediaType.JSX; + // If JS or JSX perform substitution for types if available + if (isJsOrJsx) { + if (importedFile.typeHeaders.length > 0) { + const typeHeaders = importedFile.typeHeaders[0]; + mappedUrl = typeHeaders.resolvedSpecifier; + } else if (importDesc.resolvedTypeDirective) { + mappedUrl = importDesc.resolvedTypeDirective; + } else if (importedFile.typesDirectives.length > 0) { + const typeDirective = importedFile.typesDirectives[0]; + mappedUrl = typeDirective.resolvedSpecifier; + } + } + + mappedUrl = mappedUrl.replace("memory://", ""); + SourceFile.cacheResolvedUrl(mappedUrl, importDesc.specifier, entry.url); + } + for (const fileRef of entry.referencedFiles) { + SourceFile.cacheResolvedUrl( + fileRef.resolvedSpecifier.replace("memory://", ""), + fileRef.specifier, + entry.url, + ); + } + for (const fileRef of entry.libDirectives) { + SourceFile.cacheResolvedUrl( + fileRef.resolvedSpecifier.replace("memory://", ""), + fileRef.specifier, + entry.url, + ); + } + } + } + + function buildSourceFileCache( + sourceFileMap, + ) { + for (const entry of Object.values(sourceFileMap)) { + SourceFile.addToCache({ + url: entry.url, + filename: entry.url, + mediaType: entry.mediaType, + sourceCode: entry.sourceCode, + versionHash: entry.versionHash, + }); + + for (const importDesc of entry.imports) { + let mappedUrl = importDesc.resolvedSpecifier; + const importedFile = sourceFileMap[importDesc.resolvedSpecifier]; + // IMPORTANT: due to HTTP redirects we might end up in situation + // where URL points to a file with completely different URL. + // In that case we take value of `redirect` field and cache + // resolved specifier pointing to the value of the redirect. + // It's not very elegant solution and should be rethinked. + assert(importedFile); + if (importedFile.redirect) { + mappedUrl = importedFile.redirect; + } + const isJsOrJsx = importedFile.mediaType === MediaType.JavaScript || + importedFile.mediaType === MediaType.JSX; + // If JS or JSX perform substitution for types if available + if (isJsOrJsx) { + if (importedFile.typeHeaders.length > 0) { + const typeHeaders = importedFile.typeHeaders[0]; + mappedUrl = typeHeaders.resolvedSpecifier; + } else if (importDesc.resolvedTypeDirective) { + mappedUrl = importDesc.resolvedTypeDirective; + } else if (importedFile.typesDirectives.length > 0) { + const typeDirective = importedFile.typesDirectives[0]; + mappedUrl = typeDirective.resolvedSpecifier; + } + } + + SourceFile.cacheResolvedUrl(mappedUrl, importDesc.specifier, entry.url); + } + for (const fileRef of entry.referencedFiles) { + SourceFile.cacheResolvedUrl( + fileRef.resolvedSpecifier, + fileRef.specifier, + entry.url, + ); + } + for (const fileRef of entry.libDirectives) { + SourceFile.cacheResolvedUrl( + fileRef.resolvedSpecifier, + fileRef.specifier, + entry.url, + ); + } + } + } + + // Warning! The values in this enum are duplicated in `cli/msg.rs` + // Update carefully! + const CompilerRequestType = { + Compile: 0, + Transpile: 1, + Bundle: 2, + RuntimeCompile: 3, + RuntimeBundle: 4, + RuntimeTranspile: 5, + }; + + function createBundleWriteFile(state) { + return function writeFile( + _fileName, + data, + sourceFiles, + ) { + assert(sourceFiles != null); + assert(state.host); + // we only support single root names for bundles + assert(state.rootNames.length === 1); + state.bundleOutput = buildBundle( + state.rootNames[0], + data, + sourceFiles, + state.host.options.target ?? ts.ScriptTarget.ESNext, + ); + }; + } + + function createCompileWriteFile( + state, + ) { + return function writeFile( + fileName, + data, + sourceFiles, + ) { + const isBuildInfo = fileName === TS_BUILD_INFO; + + if (isBuildInfo) { + assert(isBuildInfo); + state.buildInfo = data; + return; + } + + assert(sourceFiles); + assert(sourceFiles.length === 1); + state.emitMap[fileName] = { + filename: sourceFiles[0].fileName, + contents: data, + }; + }; + } + + function createRuntimeCompileWriteFile( + state, + ) { + return function writeFile( + fileName, + data, + sourceFiles, + ) { + assert(sourceFiles); + assert(sourceFiles.length === 1); + state.emitMap[fileName] = { + filename: sourceFiles[0].fileName, + contents: data, + }; + }; + } + + function convertCompilerOptions(str) { + const options = JSON.parse(str); + const out = {}; + const keys = Object.keys(options); + const files = []; + for (const key of keys) { + switch (key) { + case "jsx": + const value = options[key]; + if (value === "preserve") { + out[key] = ts.JsxEmit.Preserve; + } else if (value === "react") { + out[key] = ts.JsxEmit.React; + } else { + out[key] = ts.JsxEmit.ReactNative; + } + break; + case "module": + switch (options[key]) { + case "amd": + out[key] = ts.ModuleKind.AMD; + break; + case "commonjs": + out[key] = ts.ModuleKind.CommonJS; + break; + case "es2015": + case "es6": + out[key] = ts.ModuleKind.ES2015; + break; + case "esnext": + out[key] = ts.ModuleKind.ESNext; + break; + case "none": + out[key] = ts.ModuleKind.None; + break; + case "system": + out[key] = ts.ModuleKind.System; + break; + case "umd": + out[key] = ts.ModuleKind.UMD; + break; + default: + throw new TypeError("Unexpected module type"); + } + break; + case "target": + switch (options[key]) { + case "es3": + out[key] = ts.ScriptTarget.ES3; + break; + case "es5": + out[key] = ts.ScriptTarget.ES5; + break; + case "es6": + case "es2015": + out[key] = ts.ScriptTarget.ES2015; + break; + case "es2016": + out[key] = ts.ScriptTarget.ES2016; + break; + case "es2017": + out[key] = ts.ScriptTarget.ES2017; + break; + case "es2018": + out[key] = ts.ScriptTarget.ES2018; + break; + case "es2019": + out[key] = ts.ScriptTarget.ES2019; + break; + case "es2020": + out[key] = ts.ScriptTarget.ES2020; + break; + case "esnext": + out[key] = ts.ScriptTarget.ESNext; + break; + default: + throw new TypeError("Unexpected emit target."); + } + break; + case "types": + const types = options[key]; + assert(types); + files.push(...types); + break; + default: + out[key] = options[key]; + } + } + return { + options: out, + files: files.length ? files : undefined, + }; + } + + const ignoredDiagnostics = [ + // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is + // not a module. + 2306, + // TS1375: 'await' expressions are only allowed at the top level of a file + // when that file is a module, but this file has no imports or exports. + // Consider adding an empty 'export {}' to make this file a module. + 1375, + // TS1103: 'for-await-of' statement is only allowed within an async function + // or async generator. + 1103, + // TS2691: An import path cannot end with a '.ts' extension. Consider + // importing 'bad-module' instead. + 2691, + // TS5009: Cannot find the common subdirectory path for the input files. + 5009, + // TS5055: Cannot write file + // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js' + // because it would overwrite input file. + 5055, + // TypeScript is overly opinionated that only CommonJS modules kinds can + // support JSON imports. Allegedly this was fixed in + // Microsoft/TypeScript#26825 but that doesn't seem to be working here, + // so we will ignore complaints about this compiler setting. + 5070, + // TS7016: Could not find a declaration file for module '...'. '...' + // implicitly has an 'any' type. This is due to `allowJs` being off by + // default but importing of a JavaScript module. + 7016, + ]; + + const stats = []; + let statsStart = 0; + + function performanceStart() { + stats.length = 0; + // TODO(kitsonk) replace with performance.mark() when landed + statsStart = performance.now(); + ts.performance.enable(); + } + + function performanceProgram({ + program, + fileCount, + }) { + if (program) { + if ("getProgram" in program) { + program = program.getProgram(); + } + stats.push({ key: "Files", value: program.getSourceFiles().length }); + stats.push({ key: "Nodes", value: program.getNodeCount() }); + stats.push({ key: "Identifiers", value: program.getIdentifierCount() }); + stats.push({ key: "Symbols", value: program.getSymbolCount() }); + stats.push({ key: "Types", value: program.getTypeCount() }); + stats.push({ + key: "Instantiations", + value: program.getInstantiationCount(), + }); + } else if (fileCount != null) { + stats.push({ key: "Files", value: fileCount }); + } + const programTime = ts.performance.getDuration("Program"); + const bindTime = ts.performance.getDuration("Bind"); + const checkTime = ts.performance.getDuration("Check"); + const emitTime = ts.performance.getDuration("Emit"); + stats.push({ key: "Parse time", value: programTime }); + stats.push({ key: "Bind time", value: bindTime }); + stats.push({ key: "Check time", value: checkTime }); + stats.push({ key: "Emit time", value: emitTime }); + stats.push({ + key: "Total TS time", + value: programTime + bindTime + checkTime + emitTime, + }); + } + + function performanceEnd() { + // TODO(kitsonk) replace with performance.measure() when landed + const duration = performance.now() - statsStart; + stats.push({ key: "Compile time", value: duration }); + return stats; + } + + // TODO(Bartlomieju): this check should be done in Rust; there should be no + function processConfigureResponse( + configResult, + configPath, + ) { + const { ignoredOptions, diagnostics } = configResult; + if (ignoredOptions) { + console.warn( + yellow(`Unsupported compiler options in "${configPath}"\n`) + + cyan(` The following options were ignored:\n`) + + ` ${ignoredOptions.map((value) => bold(value)).join(", ")}`, + ); + } + return diagnostics; + } + + function normalizeString(path) { + let res = ""; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code; + for (let i = 0, len = path.length; i <= len; ++i) { + if (i < len) code = path.charCodeAt(i); + else if (code === CHAR_FORWARD_SLASH) break; + else code = CHAR_FORWARD_SLASH; + + if (code === CHAR_FORWARD_SLASH) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if ( + res.length < 2 || + lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT + ) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf("/"); + if (lastSlashIndex === -1) { + res = ""; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf("/"); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length === 2 || res.length === 1) { + res = ""; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + } else { + if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i); + else res = path.slice(lastSlash + 1, i); + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; + } + + function commonPath(paths, sep = "/") { + const [first = "", ...remaining] = paths; + if (first === "" || remaining.length === 0) { + return first.substring(0, first.lastIndexOf(sep) + 1); + } + const parts = first.split(sep); + + let endOfPrefix = parts.length; + for (const path of remaining) { + const compare = path.split(sep); + for (let i = 0; i < endOfPrefix; i++) { + if (compare[i] !== parts[i]) { + endOfPrefix = i; + } + } + + if (endOfPrefix === 0) { + return ""; + } + } + const prefix = parts.slice(0, endOfPrefix).join(sep); + return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`; + } + + let rootExports; + + function normalizeUrl(rootName) { + const match = /^(\S+:\/{2,3})(.+)$/.exec(rootName); + if (match) { + const [, protocol, path] = match; + return `${protocol}${normalizeString(path)}`; + } else { + return rootName; + } + } + + function buildBundle( + rootName, + data, + sourceFiles, + target, + ) { + // when outputting to AMD and a single outfile, TypeScript makes up the module + // specifiers which are used to define the modules, and doesn't expose them + // publicly, so we have to try to replicate + const sources = sourceFiles.map((sf) => sf.fileName); + const sharedPath = commonPath(sources); + rootName = normalizeUrl(rootName) + .replace(sharedPath, "") + .replace(/\.\w+$/i, ""); + // If one of the modules requires support for top-level-await, TypeScript will + // emit the execute function as an async function. When this is the case we + // need to bubble up the TLA to the instantiation, otherwise we instantiate + // synchronously. + const hasTla = data.match(/execute:\sasync\sfunction\s/); + let instantiate; + if (rootExports && rootExports.length) { + instantiate = hasTla + ? `const __exp = await __instantiate("${rootName}", true);\n` + : `const __exp = __instantiate("${rootName}", false);\n`; + for (const rootExport of rootExports) { + if (rootExport === "default") { + instantiate += `export default __exp["${rootExport}"];\n`; + } else { + instantiate += + `export const ${rootExport} = __exp["${rootExport}"];\n`; + } + } + } else { + instantiate = hasTla + ? `await __instantiate("${rootName}", true);\n` + : `__instantiate("${rootName}", false);\n`; + } + const es5Bundle = target === ts.ScriptTarget.ES3 || + target === ts.ScriptTarget.ES5 || + target === ts.ScriptTarget.ES2015 || + target === ts.ScriptTarget.ES2016; + return `${ + es5Bundle ? SYSTEM_LOADER_ES5 : SYSTEM_LOADER + }\n${data}\n${instantiate}`; + } + + function setRootExports(program, rootModule) { + // get a reference to the type checker, this will let us find symbols from + // the AST. + const checker = program.getTypeChecker(); + // get a reference to the main source file for the bundle + const mainSourceFile = program.getSourceFile(rootModule); + assert(mainSourceFile); + // retrieve the internal TypeScript symbol for this AST node + const mainSymbol = checker.getSymbolAtLocation(mainSourceFile); + if (!mainSymbol) { + return; + } + rootExports = checker + .getExportsOfModule(mainSymbol) + // .getExportsOfModule includes type only symbols which are exported from + // the module, so we need to try to filter those out. While not critical + // someone looking at the bundle would think there is runtime code behind + // that when there isn't. There appears to be no clean way of figuring that + // out, so inspecting SymbolFlags that might be present that are type only + .filter( + (sym) => + sym.flags & ts.SymbolFlags.Class || + !( + sym.flags & ts.SymbolFlags.Interface || + sym.flags & ts.SymbolFlags.TypeLiteral || + sym.flags & ts.SymbolFlags.Signature || + sym.flags & ts.SymbolFlags.TypeParameter || + sym.flags & ts.SymbolFlags.TypeAlias || + sym.flags & ts.SymbolFlags.Type || + sym.flags & ts.SymbolFlags.Namespace || + sym.flags & ts.SymbolFlags.InterfaceExcludes || + sym.flags & ts.SymbolFlags.TypeParameterExcludes || + sym.flags & ts.SymbolFlags.TypeAliasExcludes + ), + ) + .map((sym) => sym.getName()); + } + + function compile({ + allowJs, + buildInfo, + config, + configPath, + rootNames, + target, + unstable, + cwd, + sourceFileMap, + type, + performance, + }) { + if (performance) { + performanceStart(); + } + log(">>> compile start", { rootNames, type: CompilerRequestType[type] }); + + // When a programme is emitted, TypeScript will call `writeFile` with + // each file that needs to be emitted. The Deno compiler host delegates + // this, to make it easier to perform the right actions, which vary + // based a lot on the request. + const state = { + rootNames, + emitMap: {}, + }; + const host = new IncrementalCompileHost({ + bundle: false, + target, + unstable, + writeFile: createCompileWriteFile(state), + rootNames, + buildInfo, + }); + let diagnostics = []; + + host.mergeOptions({ allowJs }); + + // if there is a configuration supplied, we need to parse that + if (config && config.length && configPath) { + const configResult = host.configure(cwd, configPath, config); + diagnostics = processConfigureResponse(configResult, configPath) || []; + } + + buildSourceFileCache(sourceFileMap); + // if there was a configuration and no diagnostics with it, we will continue + // to generate the program and possibly emit it. + if (diagnostics.length === 0) { + const options = host.getCompilationSettings(); + const program = ts.createIncrementalProgram({ + rootNames, + options, + host, + }); + + // TODO(bartlomieju): check if this is ok + diagnostics = [ + ...program.getConfigFileParsingDiagnostics(), + ...program.getSyntacticDiagnostics(), + ...program.getOptionsDiagnostics(), + ...program.getGlobalDiagnostics(), + ...program.getSemanticDiagnostics(), + ]; + diagnostics = diagnostics.filter( + ({ code }) => !ignoredDiagnostics.includes(code), + ); + + // We will only proceed with the emit if there are no diagnostics. + if (diagnostics.length === 0) { + const emitResult = program.emit(); + // If `checkJs` is off we still might be compiling entry point JavaScript file + // (if it has `.ts` imports), but it won't be emitted. In that case we skip + // assertion. + if (options.checkJs) { + assert( + emitResult.emitSkipped === false, + "Unexpected skip of the emit.", + ); + } + // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned + // without casting. + diagnostics = emitResult.diagnostics; + } + performanceProgram({ program }); + } + + log("<<< compile end", { rootNames, type: CompilerRequestType[type] }); + const stats = performance ? performanceEnd() : undefined; + + return { + emitMap: state.emitMap, + buildInfo: state.buildInfo, + diagnostics: fromTypeScriptDiagnostic(diagnostics), + stats, + }; + } + + function transpile({ + config: configText, + configPath, + cwd, + performance, + sourceFiles, + }) { + if (performance) { + performanceStart(); + } + log(">>> transpile start"); + let compilerOptions; + if (configText && configPath && cwd) { + const { options, ...response } = configure( + DEFAULT_TRANSPILE_OPTIONS, + configText, + configPath, + cwd, + ); + const diagnostics = processConfigureResponse(response, configPath); + if (diagnostics && diagnostics.length) { + return { + diagnostics: fromTypeScriptDiagnostic(diagnostics), + emitMap: {}, + }; + } + compilerOptions = options; + } else { + compilerOptions = Object.assign({}, DEFAULT_TRANSPILE_OPTIONS); + } + const emitMap = {}; + let diagnostics = []; + for (const { sourceCode, fileName } of sourceFiles) { + const { + outputText, + sourceMapText, + diagnostics: diags, + } = ts.transpileModule(sourceCode, { + fileName, + compilerOptions, + reportDiagnostics: true, + }); + if (diags) { + diagnostics = diagnostics.concat(...diags); + } + emitMap[`${fileName}.js`] = { filename: fileName, contents: outputText }; + // currently we inline source maps, but this is good logic to have if this + // ever changes + if (sourceMapText) { + emitMap[`${fileName}.map`] = { + filename: fileName, + contents: sourceMapText, + }; + } + } + performanceProgram({ fileCount: sourceFiles.length }); + const stats = performance ? performanceEnd() : undefined; + log("<<< transpile end"); + return { + diagnostics: fromTypeScriptDiagnostic(diagnostics), + emitMap, + stats, + }; + } + + function bundle({ + config, + configPath, + rootNames, + target, + unstable, + cwd, + sourceFileMap, + type, + }) { + if (performance) { + performanceStart(); + } + log(">>> bundle start", { + rootNames, + type: CompilerRequestType[type], + }); + + // When a programme is emitted, TypeScript will call `writeFile` with + // each file that needs to be emitted. The Deno compiler host delegates + // this, to make it easier to perform the right actions, which vary + // based a lot on the request. + const state = { + rootNames, + bundleOutput: undefined, + }; + const host = new Host({ + bundle: true, + target, + unstable, + writeFile: createBundleWriteFile(state), + }); + state.host = host; + let diagnostics = []; + + // if there is a configuration supplied, we need to parse that + if (config && config.length && configPath) { + const configResult = host.configure(cwd, configPath, config); + diagnostics = processConfigureResponse(configResult, configPath) || []; + } + + buildSourceFileCache(sourceFileMap); + // if there was a configuration and no diagnostics with it, we will continue + // to generate the program and possibly emit it. + if (diagnostics.length === 0) { + const options = host.getCompilationSettings(); + const program = ts.createProgram({ + rootNames, + options, + host, + }); + + diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); + + // We will only proceed with the emit if there are no diagnostics. + if (diagnostics.length === 0) { + // we only support a single root module when bundling + assert(rootNames.length === 1); + setRootExports(program, rootNames[0]); + const emitResult = program.emit(); + assert( + emitResult.emitSkipped === false, + "Unexpected skip of the emit.", + ); + // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned + // without casting. + diagnostics = emitResult.diagnostics; + } + if (performance) { + performanceProgram({ program }); + } + } + + let bundleOutput; + + if (diagnostics.length === 0) { + assert(state.bundleOutput); + bundleOutput = state.bundleOutput; + } + + const stats = performance ? performanceEnd() : undefined; + + const result = { + bundleOutput, + diagnostics: fromTypeScriptDiagnostic(diagnostics), + stats, + }; + + log("<<< bundle end", { + rootNames, + type: CompilerRequestType[type], + }); + + return result; + } + + function runtimeCompile( + request, + ) { + const { options, rootNames, target, unstable, sourceFileMap } = request; + + log(">>> runtime compile start", { + rootNames, + }); + + // if there are options, convert them into TypeScript compiler options, + // and resolve any external file references + let convertedOptions; + if (options) { + const result = convertCompilerOptions(options); + convertedOptions = result.options; + } + + buildLocalSourceFileCache(sourceFileMap); + + const state = { + rootNames, + emitMap: {}, + }; + const host = new Host({ + bundle: false, + target, + writeFile: createRuntimeCompileWriteFile(state), + }); + const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS]; + if (convertedOptions) { + compilerOptions.push(convertedOptions); + } + if (unstable) { + compilerOptions.push({ + lib: [ + "deno.unstable", + ...((convertedOptions && convertedOptions.lib) || ["deno.window"]), + ], + }); + } + + host.mergeOptions(...compilerOptions); + + const program = ts.createProgram({ + rootNames, + options: host.getCompilationSettings(), + host, + }); + + const diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); + + const emitResult = program.emit(); + + assert(emitResult.emitSkipped === false, "Unexpected skip of the emit."); + + log("<<< runtime compile finish", { + rootNames, + emitMap: Object.keys(state.emitMap), + }); + + const maybeDiagnostics = diagnostics.length + ? fromTypeScriptDiagnostic(diagnostics).items + : []; + + return { + diagnostics: maybeDiagnostics, + emitMap: state.emitMap, + }; + } + + function runtimeBundle(request) { + const { options, rootNames, target, unstable, sourceFileMap } = request; + + log(">>> runtime bundle start", { + rootNames, + }); + + // if there are options, convert them into TypeScript compiler options, + // and resolve any external file references + let convertedOptions; + if (options) { + const result = convertCompilerOptions(options); + convertedOptions = result.options; + } + + buildLocalSourceFileCache(sourceFileMap); + + const state = { + rootNames, + bundleOutput: undefined, + }; + const host = new Host({ + bundle: true, + target, + writeFile: createBundleWriteFile(state), + }); + state.host = host; + + const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS]; + if (convertedOptions) { + compilerOptions.push(convertedOptions); + } + if (unstable) { + compilerOptions.push({ + lib: [ + "deno.unstable", + ...((convertedOptions && convertedOptions.lib) || ["deno.window"]), + ], + }); + } + compilerOptions.push(DEFAULT_BUNDLER_OPTIONS); + host.mergeOptions(...compilerOptions); + + const program = ts.createProgram({ + rootNames, + options: host.getCompilationSettings(), + host, + }); + + setRootExports(program, rootNames[0]); + const diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); + + const emitResult = program.emit(); + + assert(emitResult.emitSkipped === false, "Unexpected skip of the emit."); + + log("<<< runtime bundle finish", { + rootNames, + }); + + const maybeDiagnostics = diagnostics.length + ? fromTypeScriptDiagnostic(diagnostics).items + : []; + + return { + diagnostics: maybeDiagnostics, + output: state.bundleOutput, + }; + } + + function runtimeTranspile( + request, + ) { + const result = {}; + const { sources, options } = request; + const compilerOptions = options + ? Object.assign( + {}, + DEFAULT_RUNTIME_TRANSPILE_OPTIONS, + convertCompilerOptions(options).options, + ) + : DEFAULT_RUNTIME_TRANSPILE_OPTIONS; + + for (const [fileName, inputText] of Object.entries(sources)) { + const { outputText: source, sourceMapText: map } = ts.transpileModule( + inputText, + { + fileName, + compilerOptions, + }, + ); + result[fileName] = { source, map }; + } + return Promise.resolve(result); + } + + async function tsCompilerOnMessage({ + data: request, + }) { + switch (request.type) { + case CompilerRequestType.Compile: { + const result = compile(request); + globalThis.postMessage(result); + break; + } + case CompilerRequestType.Transpile: { + const result = transpile(request); + globalThis.postMessage(result); + break; + } + case CompilerRequestType.Bundle: { + const result = bundle(request); + globalThis.postMessage(result); + break; + } + case CompilerRequestType.RuntimeCompile: { + const result = runtimeCompile(request); + globalThis.postMessage(result); + break; + } + case CompilerRequestType.RuntimeBundle: { + const result = runtimeBundle(request); + globalThis.postMessage(result); + break; + } + case CompilerRequestType.RuntimeTranspile: { + const result = await runtimeTranspile(request); + globalThis.postMessage(result); + break; + } + default: + log( + `!!! unhandled CompilerRequestType: ${request.type} (${ + CompilerRequestType[request.type] + })`, + ); + } + // Shutdown after single request + globalThis.close(); + } + + function bootstrapTsCompilerRuntime() { + globalThis.bootstrap.workerRuntime("TS", false); + globalThis.onmessage = tsCompilerOnMessage; + } + + Object.defineProperties(globalThis, { + bootstrap: { + value: { + ...globalThis.bootstrap, + tsCompilerRuntime: bootstrapTsCompilerRuntime, + }, + configurable: true, + writable: true, + }, + }); +})(this); |