diff options
Diffstat (limited to 'cli/js/bundler.ts')
-rw-r--r-- | cli/js/bundler.ts | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/cli/js/bundler.ts b/cli/js/bundler.ts new file mode 100644 index 000000000..4285b61ad --- /dev/null +++ b/cli/js/bundler.ts @@ -0,0 +1,113 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +import { Console } from "./console.ts"; +import * as dispatch from "./dispatch.ts"; +import { sendSync } from "./dispatch_json.ts"; +import { TextEncoder } from "./text_encoding.ts"; +import { assert, commonPath, humanFileSize } from "./util.ts"; +import { writeFileSync } from "./write_file.ts"; + +declare global { + const console: Console; +} + +const BUNDLE_LOADER = "bundle_loader.js"; + +const encoder = new TextEncoder(); + +let bundleLoader: string; + +let rootExports: string[] | undefined; + +/** Given a fileName and the data, emit the file to the file system. */ +export function emitBundle( + rootNames: string[], + fileName: string | undefined, + data: string, + sourceFiles: readonly ts.SourceFile[] +): void { + // if the fileName is set to an internal value, just noop + if (fileName && fileName.startsWith("$deno$")) { + return; + } + // This should never happen at the moment, but this code can't currently + // support it + assert( + rootNames.length === 1, + "Only single root modules supported for bundling." + ); + if (!bundleLoader) { + bundleLoader = sendSync(dispatch.OP_FETCH_ASSET, { name: BUNDLE_LOADER }); + } + + // 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); + const rootName = rootNames[0].replace(sharedPath, "").replace(/\.\w+$/i, ""); + let instantiate: string; + if (rootExports && rootExports.length) { + instantiate = `const __rootExports = instantiate("${rootName}");\n`; + for (const rootExport of rootExports) { + if (rootExport === "default") { + instantiate += `export default __rootExports["${rootExport}"];\n`; + } else { + instantiate += `export const ${rootExport} = __rootExports["${rootExport}"];\n`; + } + } + } else { + instantiate = `instantiate("${rootName}");\n`; + } + const bundle = `${bundleLoader}\n${data}\n${instantiate}`; + if (fileName) { + const encodedData = encoder.encode(bundle); + console.warn(`Emitting bundle to "${fileName}"`); + writeFileSync(fileName, encodedData); + console.warn(`${humanFileSize(encodedData.length)} emitted.`); + } else { + console.log(bundle); + } +} + +/** Set the rootExports which will by the `emitBundle()` */ +export function setRootExports( + program: ts.Program, + rootModules: string[] +): void { + // get a reference to the type checker, this will let us find symbols from + // the AST. + const checker = program.getTypeChecker(); + assert(rootModules.length === 1); + // get a reference to the main source file for the bundle + const mainSourceFile = program.getSourceFile(rootModules[0]); + 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.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()); +} |