1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-04 08:54:20 -05:00
denoland-deno/cli/js/compiler_bundler.ts
Kitson Kelly d325566a7e Runtime Compiler API (#3442)
Also restructures the compiler TypeScript files to make them easier to
manage and eventually integrate deno_typescript fully.
2020-01-08 09:17:44 -05:00

109 lines
3.9 KiB
TypeScript

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import * as dispatch from "./dispatch.ts";
import { sendSync } from "./dispatch_json.ts";
import {
assert,
commonPath,
normalizeString,
CHAR_FORWARD_SLASH
} from "./util.ts";
const BUNDLE_LOADER = "bundle_loader.js";
/** A loader of bundled modules that we will inline into any bundle outputs. */
let bundleLoader: string;
/** Local state of what the root exports are of a root module. */
let rootExports: string[] | undefined;
/** Take a URL and normalize it, resolving relative path parts. */
function normalizeUrl(rootName: string): string {
const match = /^(\S+:\/{2,3})(.+)$/.exec(rootName);
if (match) {
const [, protocol, path] = match;
return `${protocol}${normalizeString(
path,
false,
"/",
code => code === CHAR_FORWARD_SLASH
)}`;
} else {
return rootName;
}
}
/** Given a root name, contents, and source files, enrich the data of the
* bundle with a loader and re-export the exports of the root name. */
export function buildBundle(
rootName: string,
data: string,
sourceFiles: readonly ts.SourceFile[]
): string {
// we can only do this once we are bootstrapped and easiest way to do it is
// inline here
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);
rootName = normalizeUrl(rootName)
.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`;
}
return `${bundleLoader}\n${data}\n${instantiate}`;
}
/** Set the rootExports which will by the `emitBundle()` */
export 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.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());
}