summaryrefslogtreecommitdiff
path: root/cli/js/compiler.ts
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-07-19 19:49:44 +0200
committerGitHub <noreply@github.com>2020-07-19 19:49:44 +0200
commitfa61956f03491101b6ef64423ea2f1f73af26a73 (patch)
treec3800702071ca78aa4dd71bdd0a59a9bbe460bdd /cli/js/compiler.ts
parent53adde866dd399aa2509d14508642fce37afb8f5 (diff)
Port internal TS code to JS (#6793)
Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
Diffstat (limited to 'cli/js/compiler.ts')
-rw-r--r--cli/js/compiler.ts1838
1 files changed, 0 insertions, 1838 deletions
diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts
deleted file mode 100644
index 7a56fe209..000000000
--- a/cli/js/compiler.ts
+++ /dev/null
@@ -1,1838 +0,0 @@
-// 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 single functions that should be called by Rust:
-// - `bootstrapTsCompilerRuntime`
-// This functions must be called when creating isolate
-// to properly setup runtime.
-
-// NOTE: this import has side effects!
-import "./ts_global.d.ts";
-
-import { bold, cyan, yellow } from "./colors.ts";
-import type { CompilerOptions } from "./compiler_options.ts";
-import type { Diagnostic, DiagnosticItem } from "./diagnostics.ts";
-import { fromTypeScriptDiagnostic } from "./diagnostics_util.ts";
-import type { TranspileOnlyResult } from "./ops/runtime_compiler.ts";
-import { bootstrapWorkerRuntime } from "./runtime_worker.ts";
-import { assert, log, notImplemented } from "./util.ts";
-import { core } from "./core.ts";
-
-// 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: string): string {
- 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: readonly string[] = [
- "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: ts.CompilerOptions = {
- 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: ts.CompilerOptions = {
- 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: ts.CompilerOptions = {
- 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: ts.CompilerOptions = {
- esModuleInterop: true,
- inlineSourceMap: true,
- jsx: ts.JsxEmit.React,
- module: ts.ModuleKind.ESNext,
- removeComments: true,
- target: ts.ScriptTarget.ESNext,
-};
-
-const DEFAULT_RUNTIME_COMPILE_OPTIONS: ts.CompilerOptions = {
- outDir: undefined,
-};
-
-const DEFAULT_RUNTIME_TRANSPILE_OPTIONS: ts.CompilerOptions = {
- esModuleInterop: true,
- module: ts.ModuleKind.ESNext,
- sourceMap: true,
- scriptComments: true,
- target: ts.ScriptTarget.ESNext,
-};
-
-enum CompilerHostTarget {
- Main = "main",
- Runtime = "runtime",
- Worker = "worker",
-}
-
-interface CompilerHostOptions {
- bundle?: boolean;
- target: CompilerHostTarget;
- unstable?: boolean;
- writeFile: WriteFileCallback;
- incremental?: boolean;
-}
-
-type IncrementalCompilerHostOptions =
- & Omit<
- CompilerHostOptions,
- "incremental"
- >
- & {
- rootNames?: string[];
- buildInfo?: string;
- };
-
-interface HostConfigureResponse {
- ignoredOptions?: string[];
- diagnostics?: ts.Diagnostic[];
-}
-
-interface ConfigureResponse extends HostConfigureResponse {
- options: ts.CompilerOptions;
-}
-
-// Warning! The values in this enum are duplicated in `cli/msg.rs`
-// Update carefully!
-enum MediaType {
- JavaScript = 0,
- JSX = 1,
- TypeScript = 2,
- TSX = 3,
- Json = 4,
- Wasm = 5,
- Unknown = 6,
-}
-
-interface SourceFileJson {
- url: string;
- filename: string;
- mediaType: MediaType;
- sourceCode: string;
- versionHash: string;
-}
-
-function getExtension(fileName: string, mediaType: MediaType): ts.Extension {
- 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: Map<string, SourceFile> = 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: Map<string, Map<string, string>> = new Map();
-
-function configure(
- defaultOptions: ts.CompilerOptions,
- source: string,
- path: string,
- cwd: string,
-): ConfigureResponse {
- const { config, error } = ts.parseConfigFileTextToJson(path, source);
- if (error) {
- return { diagnostics: [error], options: defaultOptions };
- }
- const { options, errors } = ts.convertCompilerOptionsFromJson(
- config.compilerOptions,
- cwd,
- );
- const ignoredOptions: string[] = [];
- 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 {
- extension!: ts.Extension;
- filename!: string;
-
- mediaType!: MediaType;
- processed = false;
- sourceCode?: string;
- tsSourceFile?: ts.SourceFile;
- versionHash!: string;
- url!: string;
-
- constructor(json: SourceFileJson) {
- Object.assign(this, json);
- this.extension = getExtension(this.url, this.mediaType);
- }
-
- static addToCache(json: SourceFileJson): SourceFile {
- 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: string): SourceFile | undefined {
- return SOURCE_FILE_CACHE.get(url);
- }
-
- static cacheResolvedUrl(
- resolvedUrl: string,
- rawModuleSpecifier: string,
- containingFile?: string,
- ): void {
- 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: string,
- containingFile: string,
- ): string | undefined {
- const containingCache = RESOLVED_SPECIFIER_CACHE.get(containingFile);
- if (containingCache) {
- return containingCache.get(moduleSpecifier);
- }
- return undefined;
- }
-}
-
-function getAssetInternal(filename: string): SourceFile {
- 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 implements ts.CompilerHost {
- #options = DEFAULT_COMPILE_OPTIONS;
- readonly #target: CompilerHostTarget;
- readonly #writeFile: WriteFileCallback;
- /* Deno specific APIs */
-
- constructor({
- bundle = false,
- incremental = false,
- target,
- unstable,
- writeFile,
- }: CompilerHostOptions) {
- 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(): ts.CompilerOptions {
- return this.#options;
- }
-
- configure(
- cwd: string,
- path: string,
- configurationText: string,
- ): HostConfigureResponse {
- log("compiler::host.configure", path);
- const { options, ...result } = configure(
- this.#options,
- configurationText,
- path,
- cwd,
- );
- this.#options = options;
- return result;
- }
-
- mergeOptions(...options: ts.CompilerOptions[]): ts.CompilerOptions {
- Object.assign(this.#options, ...options);
- return Object.assign({}, this.#options);
- }
-
- /* TypeScript CompilerHost APIs */
-
- fileExists(_fileName: string): boolean {
- return notImplemented();
- }
-
- getCanonicalFileName(fileName: string): string {
- return fileName;
- }
-
- getCompilationSettings(): ts.CompilerOptions {
- log("compiler::host.getCompilationSettings()");
- return this.#options;
- }
-
- getCurrentDirectory(): string {
- return "";
- }
-
- getDefaultLibFileName(_options: ts.CompilerOptions): string {
- 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(): string {
- return "\n";
- }
-
- getSourceFile(
- fileName: string,
- languageVersion: ts.ScriptTarget,
- onError?: (message: string) => void,
- shouldCreateNewSourceFile?: boolean,
- ): ts.SourceFile | undefined {
- 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: string): string | undefined {
- return notImplemented();
- }
-
- resolveModuleNames(
- moduleNames: string[],
- containingFile: string,
- ): Array<ts.ResolvedModuleFull | undefined> {
- 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: SourceFile | undefined = 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(): boolean {
- return true;
- }
-
- writeFile(
- fileName: string,
- data: string,
- _writeByteOrderMark: boolean,
- _onError?: (message: string) => void,
- sourceFiles?: readonly ts.SourceFile[],
- ): void {
- log("compiler::host.writeFile", fileName);
- this.#writeFile(fileName, data, sourceFiles);
- }
-}
-
-class IncrementalCompileHost extends Host {
- readonly #buildInfo?: string;
-
- constructor(options: IncrementalCompilerHostOptions) {
- super({ ...options, incremental: true });
- const { buildInfo } = options;
- if (buildInfo) {
- this.#buildInfo = buildInfo;
- }
- }
-
- readFile(fileName: string): string | undefined {
- 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: Host | undefined = new Host({
- target: CompilerHostTarget.Main,
- writeFile(): void {},
-});
-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: Record<string, SourceFileMapEntry>,
-): void {
- 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: Record<string, SourceFileMapEntry>,
-): void {
- 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,
- );
- }
- }
-}
-
-interface EmittedSource {
- // original filename
- filename: string;
- // compiled contents
- contents: string;
-}
-
-type WriteFileCallback = (
- fileName: string,
- data: string,
- sourceFiles?: readonly ts.SourceFile[],
-) => void;
-
-interface CompileWriteFileState {
- rootNames: string[];
- emitMap: Record<string, EmittedSource>;
- buildInfo?: string;
-}
-
-interface BundleWriteFileState {
- host?: Host;
- bundleOutput: undefined | string;
- rootNames: string[];
-}
-
-// Warning! The values in this enum are duplicated in `cli/msg.rs`
-// Update carefully!
-enum CompilerRequestType {
- Compile = 0,
- Transpile = 1,
- Bundle = 2,
- RuntimeCompile = 3,
- RuntimeBundle = 4,
- RuntimeTranspile = 5,
-}
-
-function createBundleWriteFile(state: BundleWriteFileState): WriteFileCallback {
- return function writeFile(
- _fileName: string,
- data: string,
- sourceFiles?: readonly ts.SourceFile[],
- ): void {
- 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: CompileWriteFileState,
-): WriteFileCallback {
- return function writeFile(
- fileName: string,
- data: string,
- sourceFiles?: readonly ts.SourceFile[],
- ): void {
- 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: CompileWriteFileState,
-): WriteFileCallback {
- return function writeFile(
- fileName: string,
- data: string,
- sourceFiles?: readonly ts.SourceFile[],
- ): void {
- assert(sourceFiles);
- assert(sourceFiles.length === 1);
- state.emitMap[fileName] = {
- filename: sourceFiles[0].fileName,
- contents: data,
- };
- };
-}
-interface ConvertCompilerOptionsResult {
- files?: string[];
- options: ts.CompilerOptions;
-}
-
-function convertCompilerOptions(str: string): ConvertCompilerOptionsResult {
- const options: CompilerOptions = JSON.parse(str);
- const out: Record<string, unknown> = {};
- const keys = Object.keys(options) as Array<keyof CompilerOptions>;
- const files: string[] = [];
- 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 as ts.CompilerOptions,
- 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,
-];
-
-type Stats = Array<{ key: string; value: number }>;
-
-const stats: Stats = [];
-let statsStart = 0;
-
-function performanceStart(): void {
- stats.length = 0;
- // TODO(kitsonk) replace with performance.mark() when landed
- statsStart = performance.now();
- ts.performance.enable();
-}
-
-function performanceProgram({
- program,
- fileCount,
-}: {
- program?: ts.Program | ts.BuilderProgram;
- fileCount?: number;
-}): void {
- 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(): Stats {
- // 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: HostConfigureResponse,
- configPath: string,
-): ts.Diagnostic[] | undefined {
- 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): string => bold(value)).join(", ")}`,
- );
- }
- return diagnostics;
-}
-
-function normalizeString(path: string): string {
- let res = "";
- let lastSegmentLength = 0;
- let lastSlash = -1;
- let dots = 0;
- let code: number;
- 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: string[], sep = "/"): string {
- 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: string[] | undefined;
-
-function normalizeUrl(rootName: string): string {
- const match = /^(\S+:\/{2,3})(.+)$/.exec(rootName);
- if (match) {
- const [, protocol, path] = match;
- return `${protocol}${normalizeString(path)}`;
- } else {
- return rootName;
- }
-}
-
-function buildBundle(
- rootName: string,
- data: string,
- sourceFiles: readonly ts.SourceFile[],
- target: ts.ScriptTarget,
-): string {
- // 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: string;
- 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: ts.Program, rootModule: string): void {
- // 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());
-}
-
-interface ImportDescriptor {
- specifier: string;
- resolvedSpecifier: string;
- typeDirective?: string;
- resolvedTypeDirective?: string;
-}
-
-interface ReferenceDescriptor {
- specifier: string;
- resolvedSpecifier: string;
-}
-
-interface SourceFileMapEntry {
- // fully resolved URL
- url: string;
- sourceCode: string;
- mediaType: MediaType;
- redirect?: string;
- imports: ImportDescriptor[];
- referencedFiles: ReferenceDescriptor[];
- libDirectives: ReferenceDescriptor[];
- typesDirectives: ReferenceDescriptor[];
- typeHeaders: ReferenceDescriptor[];
- versionHash: string;
-}
-
-/** Used when "deno run" is invoked */
-interface CompileRequest {
- type: CompilerRequestType.Compile;
- allowJs: boolean;
- target: CompilerHostTarget;
- rootNames: string[];
- configPath?: string;
- config?: string;
- unstable: boolean;
- performance: boolean;
- cwd: string;
- // key value is fully resolved URL
- sourceFileMap: Record<string, SourceFileMapEntry>;
- buildInfo?: string;
-}
-
-interface TranspileRequest {
- type: CompilerRequestType.Transpile;
- config?: string;
- configPath?: string;
- cwd?: string;
- performance: boolean;
- sourceFiles: TranspileSourceFile[];
-}
-
-interface TranspileSourceFile {
- sourceCode: string;
- fileName: string;
-}
-
-/** Used when "deno bundle" is invoked */
-interface BundleRequest {
- type: CompilerRequestType.Bundle;
- target: CompilerHostTarget;
- rootNames: string[];
- configPath?: string;
- config?: string;
- unstable: boolean;
- performance: boolean;
- cwd: string;
- // key value is fully resolved URL
- sourceFileMap: Record<string, SourceFileMapEntry>;
-}
-
-/** Used when "Deno.compile()" API is called */
-interface RuntimeCompileRequest {
- type: CompilerRequestType.RuntimeCompile;
- target: CompilerHostTarget;
- rootNames: string[];
- sourceFileMap: Record<string, SourceFileMapEntry>;
- unstable?: boolean;
- options?: string;
-}
-
-/** Used when "Deno.bundle()" API is called */
-interface RuntimeBundleRequest {
- type: CompilerRequestType.RuntimeBundle;
- target: CompilerHostTarget;
- rootNames: string[];
- sourceFileMap: Record<string, SourceFileMapEntry>;
- unstable?: boolean;
- options?: string;
-}
-
-/** Used when "Deno.transpileOnly()" API is called */
-interface RuntimeTranspileRequest {
- type: CompilerRequestType.RuntimeTranspile;
- sources: Record<string, string>;
- options?: string;
-}
-
-type CompilerRequest =
- | CompileRequest
- | TranspileRequest
- | BundleRequest
- | RuntimeCompileRequest
- | RuntimeBundleRequest
- | RuntimeTranspileRequest;
-
-interface CompileResponse {
- emitMap: Record<string, EmittedSource>;
- diagnostics: Diagnostic;
- buildInfo?: string;
- stats?: Stats;
-}
-
-interface TranspileResponse {
- emitMap: Record<string, EmittedSource>;
- diagnostics: Diagnostic;
- stats?: Stats;
-}
-
-interface BundleResponse {
- bundleOutput?: string;
- diagnostics: Diagnostic;
- stats?: Stats;
-}
-
-interface RuntimeCompileResponse {
- emitMap: Record<string, EmittedSource>;
- diagnostics: DiagnosticItem[];
-}
-
-interface RuntimeBundleResponse {
- output?: string;
- diagnostics: DiagnosticItem[];
-}
-
-function compile({
- allowJs,
- buildInfo,
- config,
- configPath,
- rootNames,
- target,
- unstable,
- cwd,
- sourceFileMap,
- type,
- performance,
-}: CompileRequest): CompileResponse {
- 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: CompileWriteFileState = {
- rootNames,
- emitMap: {},
- };
- const host = new IncrementalCompileHost({
- bundle: false,
- target,
- unstable,
- writeFile: createCompileWriteFile(state),
- rootNames,
- buildInfo,
- });
- let diagnostics: readonly ts.Diagnostic[] = [];
-
- 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,
-}: TranspileRequest): TranspileResponse {
- if (performance) {
- performanceStart();
- }
- log(">>> transpile start");
- let compilerOptions: ts.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: Record<string, EmittedSource> = {};
- let diagnostics: ts.Diagnostic[] = [];
- 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,
-}: BundleRequest): BundleResponse {
- 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: BundleWriteFileState = {
- rootNames,
- bundleOutput: undefined,
- };
- const host = new Host({
- bundle: true,
- target,
- unstable,
- writeFile: createBundleWriteFile(state),
- });
- state.host = host;
- let diagnostics: readonly ts.Diagnostic[] = [];
-
- // 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: BundleResponse = {
- bundleOutput,
- diagnostics: fromTypeScriptDiagnostic(diagnostics),
- stats,
- };
-
- log("<<< bundle end", {
- rootNames,
- type: CompilerRequestType[type],
- });
-
- return result;
-}
-
-function runtimeCompile(
- request: RuntimeCompileRequest,
-): RuntimeCompileResponse {
- 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: ts.CompilerOptions | undefined;
- if (options) {
- const result = convertCompilerOptions(options);
- convertedOptions = result.options;
- }
-
- buildLocalSourceFileCache(sourceFileMap);
-
- const state: CompileWriteFileState = {
- 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: RuntimeBundleRequest): RuntimeBundleResponse {
- 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: ts.CompilerOptions | undefined;
- if (options) {
- const result = convertCompilerOptions(options);
- convertedOptions = result.options;
- }
-
- buildLocalSourceFileCache(sourceFileMap);
-
- const state: BundleWriteFileState = {
- 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: RuntimeTranspileRequest,
-): Promise<Record<string, TranspileOnlyResult>> {
- const result: Record<string, TranspileOnlyResult> = {};
- 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,
-}: {
- data: CompilerRequest;
-}): Promise<void> {
- 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 as CompilerRequest).type
- } (${CompilerRequestType[(request as CompilerRequest).type]})`,
- );
- }
- // Shutdown after single request
- globalThis.close();
-}
-
-function bootstrapTsCompilerRuntime(): void {
- bootstrapWorkerRuntime("TS", false);
- globalThis.onmessage = tsCompilerOnMessage;
-}
-
-// 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 as any).__proto__;
-
-Object.defineProperties(globalThis, {
- bootstrap: {
- value: {
- ...globalThis.bootstrap,
- tsCompilerRuntime: bootstrapTsCompilerRuntime,
- },
- configurable: true,
- writable: true,
- },
-});