mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
6431622a6d
This PR fixes an issue where we recursively analysed imports on plain JS files in the compiler irrespective of "checkJs" being true. This caused problems where when analysing the imports of those files, we would mistake some import like structures (AMD/CommonJS) as dependencies and try to resolve the "modules" even though the compiler would not actually look at those files.
362 lines
11 KiB
TypeScript
362 lines
11 KiB
TypeScript
// 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 } 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<string, string>;
|
|
bundle?: boolean;
|
|
options?: string;
|
|
}
|
|
|
|
interface CompilerRequestRuntimeTranspile {
|
|
type: CompilerRequestType.RuntimeTranspile;
|
|
sources: Record<string, string>;
|
|
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<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]
|
|
});
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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]),
|
|
undefined,
|
|
host.getCompilationSettings().checkJs
|
|
);
|
|
|
|
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<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 };
|
|
}
|
|
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<void> {
|
|
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;
|
|
}
|
|
|
|
function bootstrapWasmCompilerRuntime(): void {
|
|
bootstrapWorkerRuntime("WASM");
|
|
globalThis.onmessage = wasmCompilerOnMessage;
|
|
}
|
|
|
|
Object.defineProperties(globalThis, {
|
|
bootstrapWasmCompilerRuntime: {
|
|
value: bootstrapWasmCompilerRuntime,
|
|
enumerable: false,
|
|
writable: false,
|
|
configurable: false
|
|
},
|
|
bootstrapTsCompilerRuntime: {
|
|
value: bootstrapTsCompilerRuntime,
|
|
enumerable: false,
|
|
writable: false,
|
|
configurable: false
|
|
}
|
|
});
|