// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // TODO(ry) Combine this implementation with //deno_typescript/compiler_main.js // 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 two functions that should be called by Rust: // - `bootstrapTsCompilerRuntime` // - `bootstrapWasmCompilerRuntime` // Either of these functions must be called when creating isolate // to properly setup runtime. // NOTE: this import has side effects! import "./ts_global.d.ts"; import { TranspileOnlyResult } from "./compiler_api.ts"; import { TS_SNAPSHOT_PROGRAM } from "./compiler_bootstrap.ts"; import { setRootExports } from "./compiler_bundler.ts"; import { CompilerHostTarget, defaultBundlerOptions, defaultRuntimeCompileOptions, defaultTranspileOptions, Host } from "./compiler_host.ts"; import { processImports, processLocalImports, resolveModules } from "./compiler_imports.ts"; import { createWriteFile, CompilerRequestType, convertCompilerOptions, ignoredDiagnostics, WriteFileState, processConfigureResponse } from "./compiler_util.ts"; import { Diagnostic } from "./diagnostics.ts"; import { fromTypeScriptDiagnostic } from "./diagnostics_util.ts"; import { assert } from "./util.ts"; import * as util from "./util.ts"; import { bootstrapWorkerRuntime, runWorkerMessageLoop } from "./runtime_worker.ts"; interface CompilerRequestCompile { type: CompilerRequestType.Compile; target: CompilerHostTarget; rootNames: string[]; // TODO(ry) add compiler config to this interface. // options: ts.CompilerOptions; configPath?: string; config?: string; bundle?: boolean; outFile?: string; } interface CompilerRequestRuntimeCompile { type: CompilerRequestType.RuntimeCompile; target: CompilerHostTarget; rootName: string; sources?: Record; bundle?: boolean; options?: string; } interface CompilerRequestRuntimeTranspile { type: CompilerRequestType.RuntimeTranspile; sources: Record; options?: string; } /** The format of the work message payload coming from the privileged side */ type CompilerRequest = | CompilerRequestCompile | CompilerRequestRuntimeCompile | CompilerRequestRuntimeTranspile; /** The format of the result sent back when doing a compilation. */ interface CompileResult { emitSkipped: boolean; diagnostics?: Diagnostic; } // TODO(bartlomieju): refactor this function into multiple functions // per CompilerRequestType async function tsCompilerOnMessage({ data: request }: { data: CompilerRequest; }): Promise { switch (request.type) { // `Compile` are requests from the internals to Deno, generated by both // the `run` and `bundle` sub command. case CompilerRequestType.Compile: { const { bundle, config, configPath, outFile, rootNames, target } = request; util.log(">>> compile start", { rootNames, type: CompilerRequestType[request.type] }); // This will recursively analyse all the code for other imports, // requesting those from the privileged side, populating the in memory // cache which will be used by the host, before resolving. const resolvedRootModules = await processImports( rootNames.map(rootName => [rootName, rootName]) ); // 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. For a `Compile` request, we need to // cache all the files in the privileged side if we aren't bundling, // and if we are bundling we need to enrich the bundle and either write // out the bundle or log it to the console. const state: WriteFileState = { type: request.type, bundle, host: undefined, outFile, rootNames }; const writeFile = createWriteFile(state); const host = (state.host = new Host({ bundle, target, writeFile })); let diagnostics: readonly ts.Diagnostic[] | undefined; // if there is a configuration supplied, we need to parse that if (config && config.length && configPath) { const configResult = host.configure(configPath, config); diagnostics = processConfigureResponse(configResult, configPath); } let emitSkipped = true; // if there was a configuration and no diagnostics with it, we will continue // to generate the program and possibly emit it. if (!diagnostics || (diagnostics && diagnostics.length === 0)) { const options = host.getCompilationSettings(); const program = ts.createProgram({ rootNames, options, host, oldProgram: TS_SNAPSHOT_PROGRAM }); diagnostics = ts .getPreEmitDiagnostics(program) .filter(({ code }) => !ignoredDiagnostics.includes(code)); // We will only proceed with the emit if there are no diagnostics. if (diagnostics && diagnostics.length === 0) { if (bundle) { // we only support a single root module when bundling assert(resolvedRootModules.length === 1); // warning so it goes to stderr instead of stdout console.warn(`Bundling "${resolvedRootModules[0]}"`); setRootExports(program, resolvedRootModules[0]); } const emitResult = program.emit(); emitSkipped = emitResult.emitSkipped; // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned // without casting. diagnostics = emitResult.diagnostics; } } const result: CompileResult = { emitSkipped, diagnostics: diagnostics.length ? fromTypeScriptDiagnostic(diagnostics) : undefined }; globalThis.postMessage(result); util.log("<<< compile end", { rootNames, type: CompilerRequestType[request.type] }); break; } case CompilerRequestType.RuntimeCompile: { // `RuntimeCompile` are requests from a runtime user, both compiles and // bundles. The process is similar to a request from the privileged // side, but also returns the output to the on message. const { rootName, sources, options, bundle, target } = request; util.log(">>> runtime compile start", { rootName, bundle, sources: sources ? Object.keys(sources) : undefined }); const resolvedRootName = sources ? rootName : resolveModules([rootName])[0]; const rootNames = sources ? processLocalImports(sources, [[resolvedRootName, resolvedRootName]]) : await processImports([[resolvedRootName, resolvedRootName]]); const state: WriteFileState = { type: request.type, bundle, host: undefined, rootNames, sources, emitMap: {}, emitBundle: undefined }; const writeFile = createWriteFile(state); const host = (state.host = new Host({ bundle, target, writeFile })); const compilerOptions = [defaultRuntimeCompileOptions]; if (options) { compilerOptions.push(convertCompilerOptions(options)); } if (bundle) { compilerOptions.push(defaultBundlerOptions); } host.mergeOptions(...compilerOptions); const program = ts.createProgram({ rootNames, options: host.getCompilationSettings(), host, oldProgram: TS_SNAPSHOT_PROGRAM }); if (bundle) { 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."); const result = [ diagnostics.length ? fromTypeScriptDiagnostic(diagnostics).items : undefined, bundle ? state.emitBundle : state.emitMap ]; globalThis.postMessage(result); assert(state.emitMap); util.log("<<< runtime compile finish", { rootName, sources: sources ? Object.keys(sources) : undefined, bundle, emitMap: Object.keys(state.emitMap) }); break; } case CompilerRequestType.RuntimeTranspile: { const result: Record = {}; const { sources, options } = request; const compilerOptions = options ? Object.assign( {}, defaultTranspileOptions, convertCompilerOptions(options) ) : defaultTranspileOptions; for (const [fileName, inputText] of Object.entries(sources)) { const { outputText: source, sourceMapText: map } = ts.transpileModule( inputText, { fileName, compilerOptions } ); result[fileName] = { source, map }; } globalThis.postMessage(result); break; } default: util.log( `!!! unhandled CompilerRequestType: ${ (request as CompilerRequest).type } (${CompilerRequestType[(request as CompilerRequest).type]})` ); } // The compiler isolate exits after a single message. globalThis.close(); } async function wasmCompilerOnMessage({ data: binary }: { data: string; }): Promise { const buffer = util.base64ToUint8Array(binary); // @ts-ignore const compiled = await WebAssembly.compile(buffer); util.log(">>> WASM compile start"); const importList = Array.from( // @ts-ignore new Set(WebAssembly.Module.imports(compiled).map(({ module }) => module)) ); const exportList = Array.from( // @ts-ignore new Set(WebAssembly.Module.exports(compiled).map(({ name }) => name)) ); globalThis.postMessage({ importList, exportList }); util.log("<<< WASM compile end"); // The compiler isolate exits after a single message. globalThis.close(); } function bootstrapTsCompilerRuntime(): void { bootstrapWorkerRuntime("TS"); globalThis.onmessage = tsCompilerOnMessage; runWorkerMessageLoop(); } function bootstrapWasmCompilerRuntime(): void { bootstrapWorkerRuntime("WASM"); globalThis.onmessage = wasmCompilerOnMessage; runWorkerMessageLoop(); } Object.defineProperties(globalThis, { bootstrapWasmCompilerRuntime: { value: bootstrapWasmCompilerRuntime, enumerable: false, writable: false, configurable: false }, bootstrapTsCompilerRuntime: { value: bootstrapTsCompilerRuntime, enumerable: false, writable: false, configurable: false } });