diff options
Diffstat (limited to 'cli/js/compiler.ts')
-rw-r--r-- | cli/js/compiler.ts | 520 |
1 files changed, 265 insertions, 255 deletions
diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts index 2807422e2..394c6cf52 100644 --- a/cli/js/compiler.ts +++ b/cli/js/compiler.ts @@ -1,12 +1,20 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // TODO(ry) Combine this implementation with //deno_typescript/compiler_main.js -// these are imported for their side effects -import "./globals.ts"; +// 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 { oldProgram } from "./compiler_bootstrap.ts"; +import { TS_SNAPSHOT_PROGRAM } from "./compiler_bootstrap.ts"; import { setRootExports } from "./compiler_bundler.ts"; import { CompilerHostTarget, @@ -30,16 +38,12 @@ import { } from "./compiler_util.ts"; import { Diagnostic } from "./diagnostics.ts"; import { fromTypeScriptDiagnostic } from "./diagnostics_util.ts"; -import * as os from "./os.ts"; import { assert } from "./util.ts"; import * as util from "./util.ts"; import { - postMessage, - workerClose, - bootstrapWorkerRuntime -} from "./worker_main.ts"; - -const self = globalThis; + bootstrapWorkerRuntime, + runWorkerMessageLoop +} from "./runtime_worker.ts"; interface CompilerRequestCompile { type: CompilerRequestType.Compile; @@ -80,274 +84,280 @@ interface CompileResult { diagnostics?: Diagnostic; } -// bootstrap the runtime environment, this gets called as the isolate is setup -self.bootstrapCompilerRuntime = function bootstrapCompilerRuntime( - compilerType: string -): void { - os.start(true, compilerType); -}; - -// bootstrap the worker environment, this gets called as the isolate is setup -self.bootstrapWorkerRuntime = bootstrapWorkerRuntime; - -// provide the "main" function that will be called by the privileged side when -// lazy instantiating the compiler web worker -self.bootstrapTsCompiler = function tsCompilerMain(): void { - // bootstrapWorkerRuntime should have already been called since a compiler is a worker. - self.onmessage = async ({ - data: request - }: { - data: CompilerRequest; - }): Promise<void> => { - 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", { +// TODO(bartlomieju): refactor this function into multiple functions +// per CompilerRequestType +async function tsCompilerOnMessage({ + data: request +}: { + data: CompilerRequest; +}): Promise<void> { + 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, - type: CompilerRequestType[request.type] + options, + host, + oldProgram: TS_SNAPSHOT_PROGRAM }); - // 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); - } + diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); - 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 - }); - - 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; + // 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 - }; - postMessage(result); - - util.log("<<< compile end", { - rootNames, - type: CompilerRequestType[request.type] - }); - break; + 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)); } - 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 - }); + if (bundle) { + compilerOptions.push(defaultBundlerOptions); + } + host.mergeOptions(...compilerOptions); - const resolvedRootName = sources - ? rootName - : resolveModules([rootName])[0]; + const program = ts.createProgram({ + rootNames, + options: host.getCompilationSettings(), + host, + oldProgram: TS_SNAPSHOT_PROGRAM + }); - const rootNames = sources - ? processLocalImports(sources, [[resolvedRootName, resolvedRootName]]) - : await processImports([[resolvedRootName, resolvedRootName]]); + if (bundle) { + setRootExports(program, rootNames[0]); + } - 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 diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); - const program = ts.createProgram({ - rootNames, - options: host.getCompilationSettings(), - host, - oldProgram - }); + const emitResult = program.emit(); - if (bundle) { - setRootExports(program, rootNames[0]); - } + assert(emitResult.emitSkipped === false, "Unexpected skip of the emit."); + const result = [ + diagnostics.length ? fromTypeScriptDiagnostic(diagnostics) : undefined, + bundle ? state.emitBundle : state.emitMap + ]; + globalThis.postMessage(result); - const diagnostics = ts - .getPreEmitDiagnostics(program) - .filter(({ code }) => !ignoredDiagnostics.includes(code)); + assert(state.emitMap); + util.log("<<< runtime compile finish", { + rootName, + sources: sources ? Object.keys(sources) : undefined, + bundle, + emitMap: Object.keys(state.emitMap) + }); - const emitResult = program.emit(); - - assert( - emitResult.emitSkipped === false, - "Unexpected skip of the emit." + break; + } + case CompilerRequestType.RuntimeTranspile: { + const result: Record<string, TranspileOnlyResult> = {}; + 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 + } ); - const result = [ - diagnostics.length - ? fromTypeScriptDiagnostic(diagnostics) - : undefined, - bundle ? state.emitBundle : state.emitMap - ]; - postMessage(result); - - assert(state.emitMap); - util.log("<<< runtime compile finish", { - rootName, - sources: sources ? Object.keys(sources) : undefined, - bundle, - emitMap: Object.keys(state.emitMap) - }); - - break; + result[fileName] = { source, map }; } - case CompilerRequestType.RuntimeTranspile: { - const result: Record<string, TranspileOnlyResult> = {}; - 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 }; - } - postMessage(result); + globalThis.postMessage(result); - break; - } - default: - util.log( - `!!! unhandled CompilerRequestType: ${ - (request as CompilerRequest).type - } (${CompilerRequestType[(request as CompilerRequest).type]})` - ); + break; } + default: + util.log( + `!!! unhandled CompilerRequestType: ${ + (request as CompilerRequest).type + } (${CompilerRequestType[(request as CompilerRequest).type]})` + ); + } + + // The compiler isolate exits after a single message. + globalThis.workerClose(); +} + +async function wasmCompilerOnMessage({ + data: binary +}: { + data: string; +}): Promise<void> { + const buffer = util.base64ToUint8Array(binary); + // @ts-ignore + const compiled = await WebAssembly.compile(buffer); + + util.log(">>> WASM compile start"); - // The compiler isolate exits after a single message. - workerClose(); - }; -}; - -self.bootstrapWasmCompiler = function wasmCompilerMain(): void { - // bootstrapWorkerRuntime should have already been called since a compiler is a worker. - self.onmessage = async ({ - data: binary - }: { - data: string; - }): Promise<void> => { - const buffer = util.base64ToUint8Array(binary); + const importList = Array.from( // @ts-ignore - const compiled = await WebAssembly.compile(buffer); + new Set(WebAssembly.Module.imports(compiled).map(({ module }) => module)) + ); + const exportList = Array.from( + // @ts-ignore + new Set(WebAssembly.Module.exports(compiled).map(({ name }) => name)) + ); - util.log(">>> WASM compile start"); + globalThis.postMessage({ importList, exportList }); - 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)) - ); + util.log("<<< WASM compile end"); - postMessage({ importList, exportList }); + // The compiler isolate exits after a single message. + globalThis.workerClose(); +} - util.log("<<< WASM compile end"); +function bootstrapTsCompilerRuntime(): void { + bootstrapWorkerRuntime("TS"); + globalThis.onmessage = tsCompilerOnMessage; + runWorkerMessageLoop(); +} + +function bootstrapWasmCompilerRuntime(): void { + bootstrapWorkerRuntime("WASM"); + globalThis.onmessage = wasmCompilerOnMessage; + runWorkerMessageLoop(); +} - // The compiler isolate exits after a single message. - workerClose(); - }; -}; +Object.defineProperties(globalThis, { + bootstrapWasmCompilerRuntime: { + value: bootstrapWasmCompilerRuntime, + enumerable: false, + writable: false, + configurable: false + }, + bootstrapTsCompilerRuntime: { + value: bootstrapTsCompilerRuntime, + enumerable: false, + writable: false, + configurable: false + } +}); |