mirror of
https://github.com/denoland/deno.git
synced 2024-11-04 08:54:20 -05:00
180 lines
5.6 KiB
TypeScript
180 lines
5.6 KiB
TypeScript
|
// 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;
|
||
|
}
|