diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2019-06-09 04:42:28 +1000 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2019-06-08 14:42:28 -0400 |
commit | 307e0927531025dd707a0f4dc3fd549b65598eb2 (patch) | |
tree | ad006d9b82d94b8d0f32e0bcef48eb5f1f531cc6 /js | |
parent | 35f879ad32bc7fddb1c20e9a4154f42c7b9b8350 (diff) |
Add 'bundle' subcommand. (#2467)
Diffstat (limited to 'js')
-rw-r--r-- | js/compiler.ts | 221 |
1 files changed, 147 insertions, 74 deletions
diff --git a/js/compiler.ts b/js/compiler.ts index 6b0881700..db59a8bd8 100644 --- a/js/compiler.ts +++ b/js/compiler.ts @@ -1,20 +1,22 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import * as msg from "gen/cli/msg_generated"; +import * as ts from "typescript"; + +import { assetSourceCode } from "./assets"; +import { bold, cyan, yellow } from "./colors"; +import { Console } from "./console"; import { core } from "./core"; import { Diagnostic, fromTypeScriptDiagnostic } from "./diagnostics"; -import * as flatbuffers from "./flatbuffers"; +import { cwd } from "./dir"; import { sendSync } from "./dispatch"; -import { TextDecoder } from "./text_encoding"; -import * as ts from "typescript"; +import * as flatbuffers from "./flatbuffers"; import * as os from "./os"; -import { bold, cyan, yellow } from "./colors"; -import { window } from "./window"; -import { postMessage, workerClose, workerMain } from "./workers"; -import { Console } from "./console"; +import { TextDecoder, TextEncoder } from "./text_encoding"; import { assert, notImplemented } from "./util"; import * as util from "./util"; -import { cwd } from "./dir"; -import { assetSourceCode } from "./assets"; +import { window } from "./window"; +import { postMessage, workerClose, workerMain } from "./workers"; +import { writeFileSync } from "./write_file"; // Startup boilerplate. This is necessary because the compiler has its own // snapshot. (It would be great if we could remove these things or centralize @@ -32,6 +34,7 @@ const OUT_DIR = "$deno$"; /** The format of the work message payload coming from the privileged side */ interface CompilerReq { rootNames: string[]; + bundle?: string; // TODO(ry) add compiler config to this interface. // options: ts.CompilerOptions; configPath?: string; @@ -116,6 +119,7 @@ interface EmitResult { diagnostics?: Diagnostic; } +/** Ops to Rust to resolve and fetch a modules meta data. */ function fetchModuleMetaData( specifier: string, referrer: string @@ -151,7 +155,23 @@ function fetchModuleMetaData( }; } -/** For caching source map and compiled js */ +/** Utility function to turn the number of bytes into a human readable + * unit */ +function humanFileSize(bytes: number): string { + const thresh = 1000; + if (Math.abs(bytes) < thresh) { + return bytes + " B"; + } + const units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + let u = -1; + do { + bytes /= thresh; + ++u; + } while (Math.abs(bytes) >= thresh && u < units.length - 1); + return `${bytes.toFixed(1)} ${units[u]}`; +} + +/** Ops to rest for caching source map and compiled js */ function cache(extension: string, moduleId: string, contents: string): void { util.log("compiler.cache", moduleId); const builder = flatbuffers.createBuilder(); @@ -168,6 +188,21 @@ function cache(extension: string, moduleId: string, contents: string): void { assert(baseRes == null); } +const encoder = new TextEncoder(); + +/** Given a fileName and the data, emit the file to the file system. */ +function emitBundle(fileName: string, data: string): void { + // For internal purposes, when trying to emit to `$deno$` just no-op + if (fileName.startsWith("$deno$")) { + console.warn("skipping compiler.emitBundle", fileName); + return; + } + const encodedData = encoder.encode(data); + console.log(`Emitting bundle to "${fileName}"`); + writeFileSync(fileName, encodedData); + console.log(`${humanFileSize(encodedData.length)} emitted.`); +} + /** Returns the TypeScript Extension enum for a given media type. */ function getExtension( fileName: string, @@ -200,6 +235,46 @@ class Host implements ts.CompilerHost { target: ts.ScriptTarget.ESNext }; + private _resolveModule(specifier: string, referrer: string): ModuleMetaData { + // Handle built-in assets specially. + if (specifier.startsWith(ASSETS)) { + const moduleName = specifier.split("/").pop()!; + const assetName = moduleName.includes(".") + ? moduleName + : `${moduleName}.d.ts`; + assert(assetName in assetSourceCode, `No such asset "${assetName}"`); + const sourceCode = assetSourceCode[assetName]; + return { + moduleName, + filename: specifier, + mediaType: msg.MediaType.TypeScript, + sourceCode + }; + } + return fetchModuleMetaData(specifier, referrer); + } + + /* Deno specific APIs */ + + /** Provides the `ts.HostCompiler` interface for Deno. + * + * @param _bundle Set to a string value to configure the host to write out a + * bundle instead of caching individual files. + */ + constructor(private _bundle?: string) { + if (this._bundle) { + // options we need to change when we are generating a bundle + const bundlerOptions: ts.CompilerOptions = { + module: ts.ModuleKind.AMD, + inlineSourceMap: true, + outDir: undefined, + outFile: `${OUT_DIR}/bundle.js`, + sourceMap: false + }; + Object.assign(this._options, bundlerOptions); + } + } + /** Take a configuration string, parse it, and use it to merge with the * compiler's configuration options. The method returns an array of compiler * options which were ignored, or `undefined`. @@ -234,17 +309,32 @@ class Host implements ts.CompilerHost { }; } + /* TypeScript CompilerHost APIs */ + + fileExists(_fileName: string): boolean { + return notImplemented(); + } + + getCanonicalFileName(fileName: string): string { + // console.log("getCanonicalFileName", fileName); + return fileName; + } + getCompilationSettings(): ts.CompilerOptions { util.log("getCompilationSettings()"); return this._options; } - fileExists(_fileName: string): boolean { - return notImplemented(); + getCurrentDirectory(): string { + return ""; } - readFile(_fileName: string): string | undefined { - return notImplemented(); + getDefaultLibFileName(_options: ts.CompilerOptions): string { + return ASSETS + "/lib.deno_runtime.d.ts"; + } + + getNewLine(): string { + return "\n"; } getSourceFile( @@ -266,47 +356,8 @@ class Host implements ts.CompilerHost { ); } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return ASSETS + "/lib.deno_runtime.d.ts"; - } - - writeFile( - fileName: string, - data: string, - writeByteOrderMark: boolean, - onError?: (message: string) => void, - sourceFiles?: ReadonlyArray<ts.SourceFile> - ): void { - util.log("writeFile", fileName); - assert(sourceFiles != null && sourceFiles.length == 1); - const sourceFileName = sourceFiles![0].fileName; - - if (fileName.endsWith(".map")) { - // Source Map - cache(".map", sourceFileName, data); - } else if (fileName.endsWith(".js") || fileName.endsWith(".json")) { - // Compiled JavaScript - cache(".js", sourceFileName, data); - } else { - assert(false, "Trying to cache unhandled file type " + fileName); - } - } - - getCurrentDirectory(): string { - return ""; - } - - getCanonicalFileName(fileName: string): string { - // console.log("getCanonicalFileName", fileName); - return fileName; - } - - useCaseSensitiveFileNames(): boolean { - return true; - } - - getNewLine(): string { - return "\n"; + readFile(_fileName: string): string | undefined { + return notImplemented(); } resolveModuleNames( @@ -335,23 +386,42 @@ class Host implements ts.CompilerHost { ); } - private _resolveModule(specifier: string, referrer: string): ModuleMetaData { - // Handle built-in assets specially. - if (specifier.startsWith(ASSETS)) { - const moduleName = specifier.split("/").pop()!; - const assetName = moduleName.includes(".") - ? moduleName - : `${moduleName}.d.ts`; - assert(assetName in assetSourceCode, `No such asset "${assetName}"`); - const sourceCode = assetSourceCode[assetName]; - return { - moduleName, - filename: specifier, - mediaType: msg.MediaType.TypeScript, - sourceCode - }; + useCaseSensitiveFileNames(): boolean { + return true; + } + + writeFile( + fileName: string, + data: string, + writeByteOrderMark: boolean, + onError?: (message: string) => void, + sourceFiles?: ReadonlyArray<ts.SourceFile> + ): void { + util.log("writeFile", fileName); + try { + if (this._bundle) { + emitBundle(this._bundle, data); + } else { + assert(sourceFiles != null && sourceFiles.length == 1); + const sourceFileName = sourceFiles![0].fileName; + + if (fileName.endsWith(".map")) { + // Source Map + cache(".map", sourceFileName, data); + } else if (fileName.endsWith(".js") || fileName.endsWith(".json")) { + // Compiled JavaScript + cache(".js", sourceFileName, data); + } else { + assert(false, "Trying to cache unhandled file type " + fileName); + } + } + } catch (e) { + if (onError) { + onError(String(e)); + } else { + throw e; + } } - return fetchModuleMetaData(specifier, referrer); } } @@ -360,12 +430,12 @@ class Host implements ts.CompilerHost { window.compilerMain = function compilerMain(): void { // workerMain should have already been called since a compiler is a worker. window.onmessage = ({ data }: { data: CompilerReq }): void => { + const { rootNames, configPath, config, bundle } = data; + const host = new Host(bundle); + let emitSkipped = true; let diagnostics: ts.Diagnostic[] | undefined; - const { rootNames, configPath, config } = data; - const host = new Host(); - // if there is a configuration supplied, we need to parse that if (config && config.length && configPath) { const configResult = host.configure(configPath, config); @@ -410,6 +480,9 @@ window.compilerMain = function compilerMain(): void { // We will only proceed with the emit if there are no diagnostics. if (diagnostics && diagnostics.length === 0) { + if (bundle) { + console.log(`Bundling "${bundle}"`); + } const emitResult = program.emit(); emitSkipped = emitResult.emitSkipped; // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned |