summaryrefslogtreecommitdiff
path: root/cli/js/compiler_imports.ts
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js/compiler_imports.ts')
-rw-r--r--cli/js/compiler_imports.ts179
1 files changed, 179 insertions, 0 deletions
diff --git a/cli/js/compiler_imports.ts b/cli/js/compiler_imports.ts
new file mode 100644
index 000000000..d861f8ddc
--- /dev/null
+++ b/cli/js/compiler_imports.ts
@@ -0,0 +1,179 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+import {
+ MediaType,
+ SourceFile,
+ SourceFileJson
+} from "./compiler_sourcefile.ts";
+import { cwd } from "./dir.ts";
+import * as dispatch from "./dispatch.ts";
+import { sendAsync, sendSync } from "./dispatch_json.ts";
+import { assert } from "./util.ts";
+import * as util from "./util.ts";
+
+/** Resolve a path to the final path segment passed. */
+function resolvePath(...pathSegments: string[]): string {
+ let resolvedPath = "";
+ let resolvedAbsolute = false;
+
+ for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ let path: string;
+
+ if (i >= 0) path = pathSegments[i];
+ else path = cwd();
+
+ // Skip empty entries
+ if (path.length === 0) {
+ continue;
+ }
+
+ resolvedPath = `${path}/${resolvedPath}`;
+ resolvedAbsolute = path.charCodeAt(0) === util.CHAR_FORWARD_SLASH;
+ }
+
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when cwd() fails)
+
+ // Normalize the path
+ resolvedPath = util.normalizeString(
+ resolvedPath,
+ !resolvedAbsolute,
+ "/",
+ code => code === util.CHAR_FORWARD_SLASH
+ );
+
+ if (resolvedAbsolute) {
+ if (resolvedPath.length > 0) return `/${resolvedPath}`;
+ else return "/";
+ } else if (resolvedPath.length > 0) return resolvedPath;
+ else return ".";
+}
+
+/** Resolve a relative specifier based on the referrer. Used when resolving
+ * modules internally within the runtime compiler API. */
+function resolveSpecifier(specifier: string, referrer: string): string {
+ if (!specifier.startsWith(".")) {
+ return specifier;
+ }
+ const pathParts = referrer.split("/");
+ pathParts.pop();
+ let path = pathParts.join("/");
+ path = path.endsWith("/") ? path : `${path}/`;
+ return resolvePath(path, specifier);
+}
+
+/** Ops to Rust to resolve modules' URLs. */
+export function resolveModules(
+ specifiers: string[],
+ referrer?: string
+): string[] {
+ util.log("compiler_imports::resolveModules", { specifiers, referrer });
+ return sendSync(dispatch.OP_RESOLVE_MODULES, { specifiers, referrer });
+}
+
+/** Ops to Rust to fetch modules meta data. */
+function fetchSourceFiles(
+ specifiers: string[],
+ referrer?: string
+): Promise<SourceFileJson[]> {
+ util.log("compiler_imports::fetchSourceFiles", { specifiers, referrer });
+ return sendAsync(dispatch.OP_FETCH_SOURCE_FILES, {
+ specifiers,
+ referrer
+ });
+}
+
+/** Given a filename, determine the media type based on extension. Used when
+ * resolving modules internally in a runtime compile. */
+function getMediaType(filename: string): MediaType {
+ const maybeExtension = /\.([a-zA-Z]+)$/.exec(filename);
+ if (!maybeExtension) {
+ util.log(`!!! Could not identify valid extension: "${filename}"`);
+ return MediaType.Unknown;
+ }
+ const [, extension] = maybeExtension;
+ switch (extension.toLowerCase()) {
+ case "js":
+ return MediaType.JavaScript;
+ case "jsx":
+ return MediaType.JSX;
+ case "json":
+ return MediaType.Json;
+ case "ts":
+ return MediaType.TypeScript;
+ case "tsx":
+ return MediaType.TSX;
+ case "wasm":
+ return MediaType.Wasm;
+ default:
+ util.log(`!!! Unknown extension: "${extension}"`);
+ return MediaType.Unknown;
+ }
+}
+
+/** Recursively process the imports of modules from within the supplied sources,
+ * generating `SourceFile`s of any imported files.
+ *
+ * Specifiers are supplied in an array of tuples where the first is the
+ * specifier that will be requested in the code and the second is the specifier
+ * that should be actually resolved. */
+export function processLocalImports(
+ sources: Record<string, string>,
+ specifiers: Array<[string, string]>,
+ referrer?: string
+): string[] {
+ if (!specifiers.length) {
+ return [];
+ }
+ const moduleNames = specifiers.map(
+ referrer
+ ? ([, specifier]): string => resolveSpecifier(specifier, referrer)
+ : ([, specifier]): string => specifier
+ );
+ for (let i = 0; i < moduleNames.length; i++) {
+ const moduleName = moduleNames[i];
+ assert(moduleName in sources, `Missing module in sources: "${moduleName}"`);
+ const sourceFile =
+ SourceFile.get(moduleName) ||
+ new SourceFile({
+ url: moduleName,
+ filename: moduleName,
+ sourceCode: sources[moduleName],
+ mediaType: getMediaType(moduleName)
+ });
+ sourceFile.cache(specifiers[i][0], referrer);
+ if (!sourceFile.processed) {
+ processLocalImports(sources, sourceFile.imports(), sourceFile.url);
+ }
+ }
+ return moduleNames;
+}
+
+/** Recursively process the imports of modules, generating `SourceFile`s of any
+ * imported files.
+ *
+ * Specifiers are supplied in an array of tuples where the first is the
+ * specifier that will be requested in the code and the second is the specifier
+ * that should be actually resolved. */
+export async function processImports(
+ specifiers: Array<[string, string]>,
+ referrer?: string
+): Promise<string[]> {
+ if (!specifiers.length) {
+ return [];
+ }
+ const sources = specifiers.map(([, moduleSpecifier]) => moduleSpecifier);
+ const resolvedSources = resolveModules(sources, referrer);
+ const sourceFiles = await fetchSourceFiles(resolvedSources, referrer);
+ assert(sourceFiles.length === specifiers.length);
+ for (let i = 0; i < sourceFiles.length; i++) {
+ const sourceFileJson = sourceFiles[i];
+ const sourceFile =
+ SourceFile.get(sourceFileJson.url) || new SourceFile(sourceFileJson);
+ sourceFile.cache(specifiers[i][0], referrer);
+ if (!sourceFile.processed) {
+ await processImports(sourceFile.imports(), sourceFile.url);
+ }
+ }
+ return resolvedSources;
+}